Python 线程池学习
什么是线程池
- 一个线程的生命周期为开启,运行,销毁。其中,开启和销毁线程都需要消耗性能,花费时间。当进行多线程操作时,如果线程不被复用,每次创建线程都意味着要执行整个生命周期,系统开销也随之提高,性能也会下降。
- 因此使用线程池,将预先创建好的线程放进线程池中,同时处理完当前任务后不销毁,处理下一个任务。避免多次创建线程,带来不必要的系统开销。
实现原理
创建任务队列,开启多个线程,让每个线程监听队列任务并执行完通知拿下一个任务,直到队列中的任务被取空,然后退出线程。
应用实例
线程池原型示范
queue = Queue()
# 线程执行函数,监听队列和通知task完成
def do_job():
while True:
i = queue.get()
time.sleep(1) # 阻塞
print(f"index {i}")
queue.task_done()
if __name__ == '__main__':
# 创建包括3个daemon守护线程的线程池
for i in range(3):
t = Thread(target=do_job)
t.daemon = True # 主线程退出,daemon线程也退出
t.start()
# 往队列中塞任务
for i in range(10):
queue.put(i)
queue.join() # 当任务清空 解除阻塞 向下执行
具体步骤:
- 创建任务队列,queue.queue()实例,然后通过queue.put()填充任务
- 生成守护线程池,把线程设置为daemon守护线程,daemon守护线程意味着主线程退出时,守护线程也会自动退出,如果默认False,非daemon线程会阻塞主线程退出,即使queue队列任务完成,线程池依然会阻塞无限等待任务,使得主线程不会退出。
- 当主线程使用了queue.join()的时候,说明主线程会阻塞直到queue已经是清空的。主程序如何判断queue清空,当线程queue.get()处理任务后,发生queue.task_done(),queue数据减1,直到queue的数据都是空的,queue.join()解除阻塞,向下执行。
- 根据需要觉得使用thread.join()还是queue.join();如果想做完任务队列就结束,用queue.join();如果想线程做完任务就结束,thread.join()
ThreadPoolExecutor 示范
线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个基类:ThreadPoolExecutor: 创建线程池;ProcessPoolExecutor: 创建进程池
- python有GIL,线程是单核的
- python进程每个都有独立的GIL,所以是多核的,但每个进程里的线程都需分配一定内存空间。
def thread_job(i):
print(i)
time.sleep(3) # 阻塞
from concurrent.futures import ThreadPoolExecutor
thread_pool = ThreadPoolExecutor(max_workers=2)
for i in range(1000):
thread_pool.submit(thread_job, i) #提交task
thread_pool.shutdown(wait= True) #关闭线程池,关闭阻塞
具体步骤:
- 定义函数作为线程任务
- 引用ThreadPoolExecutor构造器创建线程池实例
- 调用ThreadPoolExecutor对象调用submit()方法提交任务
- 调用ThreadPoolExecutor对象调用shutdown关闭线程池
GIL对线程池的影响
任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
参考:http://t.csdn.cn/pQNWC
参考:http://t.csdn.cn/s2A6h