本文介绍了Python多线程threading模块的基本用法。
Python3 基础教程最全总结
Python3 进阶教程最全总结
一文掌握Python基础知识
一文掌握Python列表/元组/字典/集合
一文掌握Python函数用法
Python面向对象之类与对象详解
Python面向对象之装饰器与封装详解
Python面向对象之继承和多态详解
Python异常处理和模块详解
Python文件(I/O)操作详解
Python网络编程之Socket原理与基本用法
Python多线程threading模块基本用法
Python爬虫正则表达式详解 爬爬爬爬个虫子
Python爬虫实战Urllib抓取段子
Python爬虫实战抓包分析视频评论
Python爬虫实战Requests抓取博客文章
Python爬虫实战Scrapy抓取商品信息并写入数据库
多线程(threading模块)官方文档
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
Python通过两个标准库thread和threading提供对线程的支持。threading提供了低级别的、原始的线程以及一个简单的锁。threading 模块提供的其他方法:
threading.currentThread()
:返回当前的线程变量。threading.enumerate()
:返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。threading.activeCount()
:返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。import threading
import pprint
def main():
print('当前已经激活的线程数:%s \n'% threading.active_count())
print('查看当前激活的线程:%s \n'% threading.enumerate())
print('正在运行的线程:%s \n'% threading.current_thread())
if __name__ == '__main__':
main()
输出:
当前已经激活的线程数:5
查看当前激活的线程:[<_MainThread(MainThread, started 4612)>, <Thread(Thread-4, started daemon 5424)>, <Heartbeat(Thread-5, started daemon 6312)>, <HistorySavingThread(IPythonHistorySavingThread, started 17564)>, <ParentPollerWindows(Thread-3, started daemon 14200)>]
正在运行的线程:<_MainThread(MainThread, started 4612)>
threading模块中Thread类的其它方法:
run()
: 用以表示线程活动的方法。start()
:启动线程活动。join([time])
: 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。isAlive()
: 返回线程是否活动的。getName()
: 返回线程名。setName()
: 设置线程名。import threading
import time
# 添加线程
def thread_1():
print('This is the first added Thread, ID is %s \n' % threading.current_thread())
current_time = time.strftime('%a %b %d %Y %H:%M:%S')
print('thread_1 start...,start time:%s \n' % current_time)
for i in range(5):
time.sleep(1)
print('thread_1 end,end time:%s \n' % current_time)
def thread_2():
current_time = time.strftime('%a %b %d %Y %H:%M:%S')
print('thread_2 start...,start time:%s \n' % current_time)
print('This is the second added Thread, ID is %s \n' % threading.current_thread())
print('thread_2 end,end time:%s \n' % current_time)
def main():
# 添加线程
added_thread_1 = threading.Thread(target = thread_1, name = 'T1')
added_thread_2 = threading.Thread(target = thread_2, name = 'T2')
# 启动线程
added_thread_1.start()
added_thread_2.start()
# join方法
#added_thread_1.join()
#added_thread_2.join()
print('All done! \n')
if __name__ == '__main__':
main()
输出:
This is the first added Thread, ID is <Thread(T1, started 18256)>
thread_1 start...,start time:Fri Apr 03 2020 18:35:07
thread_2 start...,start time:Fri Apr 03 2020 18:35:07
All done!
This is the second added Thread, ID is <Thread(T2, started 2404)>
thread_2 end,end time:Fri Apr 03 2020 18:35:07
thread_1 end,end time:Fri Apr 03 2020 18:35:07
由输出信息可知,多线程是同时进行的,如果想实现所有线程运行完毕,再执行主函数中的其他语句,可以使用 join()
方法。使用 join()
方法的输出:
This is the first added Thread, ID is <Thread(T1, started 9428)>
thread_1 start...,start time:Fri Apr 03 2020 14:49:46
thread_2 start...,start time:Fri Apr 03 2020 14:49:46
This is the second added Thread, ID is <Thread(T2, started 14592)>
thread_2 end,end time:Fri Apr 03 2020 14:49:46
thread_1 end,end time:Fri Apr 03 2020 14:49:46
All done!
可以看出,主函数中的其它语句在等待其他线程执行完毕之后才会运行。
Python的Queue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。
Queue模块中的常用方法:
Queue.qsize()
返回队列的大小;Queue.empty()
如果队列为空,返回True,反之False;Queue.full()
如果队列满了,返回True,反之False;Queue.full
与 maxsize 大小对应;Queue.get([block[, timeout]])
获取队列,timeout等待时间;Queue.get_nowait()
相当Queue.get(False);Queue.put(item)
写入队列,timeout等待时间;Queue.put_nowait(item)
相当 Queue.put(item, False)
;Queue.task_done()
在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号;Queue.join()
实际上意味着等到队列为空,再执行别的操作;#Queue 实现有返回值操作
import threading
import time
import queue
import numpy as np
def job(list_input, q):
for i in range(len(list_input)):
list_input[i] = list_input[i] ** 2
#return list_input
q.put(list_input)
def multi_threading(data):
q = queue.Queue()
threads = []
for i in range(4):
t = threading.Thread(target = job,
args = (data[i], q))
t.start()#启动线程
threads.append(t) #添加到线程列表
for thread in threads:
thread.join() # 将线程列表中的所有线程添加到主线程
results = []
for i_ in range(4):
#q.get() #Queue() 按照顺序每次单个取值
results.append(q.get())
print(results)
data = np.random.randint(1, 20, size=(4,3))
multi_threading(data)
输出:
[array([225, 289, 4]), array([225, 144, 16]), array([324, 81, 9]), array([ 49, 64, 196])]
GIL全称 Global Interpreter Lock,为全局解释器锁。GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。GIL 的功能是:在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。CPython 不会让一个线程一直独占解释器,它会轮流执行 Python 线程。这样一来,用户看到的就是“伪”并行,即 Python 线程在交替执行,来模拟真正并行的线程。 GIL 限制了 Python 多线程的性能不会像我们预期的那样,即多线程的运行时间不降反增。关于原理,可以访问文末最后两篇文章,这里不做过多介绍,只需知道:Python的多线程在多核CPU上,只对于IO密集型计算产生正面效果;而当有至少有一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降。Python GIL底层实现原理图如下:
Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会锁住 GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。
import threading
from queue import Queue
import copy
import time
def job(list_input, q):
res = sum(list_input)
q.put(res)
def multi_threading(list_input):
q = Queue()
threads = []
for i in range(4):
t = threading.Thread(target=job, args=(copy.copy(list_input), q), name='T%i' % i)
t.start()
threads.append(t)
[t.join() for t in threads]
total = 0
for _ in range(4):
total += q.get()
print(total)
def normal(ist_input):
total = sum(ist_input)
print(total)
if __name__ == '__main__':
ist_input = list(range(1000000))
s_t = time.time()
normal(ist_input*4)
print('normal: ',time.time()-s_t)
s_t = time.time()
multithreading(ist_input)
print('multithreading: ', time.time()-s_t)
输出:
1999998000000
normal: 0.1658337116241455
1999998000000
multithreading: 0.16408824920654297
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有 acquire()
方法和release()
方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire()
和release()
方法之间。
#lock
import threading
def job_1():
global A, lock
lock.acquire()
for i in range(10):
A += 1
print('job_1', A)
lock.release()
def job_2():
global A, lock
lock.acquire()
for i in range(10):
A += 10
print('job_2', A)
lock.release()
if __name__ == '__main__':
lock = threading.Lock()
A = 0
t1 = threading.Thread(target=job_1)
t2 = threading.Thread(target=job_2)
t1.start()
t2.start()
t1.join()
t2.join()
参考:
threading官方文档:https://docs.python.org/3.7/library/threading.html?highlight=threading#module-threading
Python time模块:https://www.runoob.com/python/python-date-time.html
queue官方文档:https://docs.python.org/3.7/library/queue.html?highlight=queue#module-queue
https://edu.aliyun.com/lesson_505_5754?spm=5176.10731542.0.0.d9ba25aeDtbi05#_5754
https://www.bilibili.com/video/BV1jW411Y7Wj
http://c.biancheng.net/view/5537.html
http://cenalulu.github.io/python/gil-in-python/