了解GIL机制
再了解多线程和多进程之前,我们有必要先了解python的GIL机制。
GIL(global interpreter lock),也叫做python的全局解释器锁。GIL导致了同一个时刻,只能有有一个线程在CPU上执行python任务。也就是是说,在单核的CPU上,无论你开启多少个python线程,CPU上始终只有一个python解释器在运作,将你的请求转化为CPU可执行的命令。这也是为什么有人说python的多线程是伪多线程。
造成这个问题的不是python语言本身,而是python的底层Cpython,而且python很多包也都是基于Cpython写的,因此这个问题,目前还没有看到有效的解决办法。
但这也并不意味着python多线程毫无意义,如果程序中很多功能是调用IO口,比如从数据库里面取数据,开启多个线程同时取数据就很有意义,因为这些程序计算量很小,CPU占用量极低,性能是被数据库本身所局限的,而非CPU。
但是如果程序的功能主要就是计算,那么开启多线程的意义就不大了,因为计算往往需要大幅度的占用CPU的资源,即使开启再多的线程,都只有一个python解释器为你工作。这时候就需要用到多进程,将python程序映射到多个CPU上进行计算,那么性能就会成倍的增加了!
所以,什么时候使用多线程要视情况而定,计算密集型使用多进程,IO密集型使用多线程。
多线程编程
1. 使用Manager().Queue() 作为队列管理的多进程
from multiprocessing import Manager, Pool
class multi_task: # 注释3
def task_function(self, q):
while not q.empty(): # 只要共享的队列参数非空,那么继续从队列中提取参数并计算
try:
print(q.qsize()) # 观察队列内的参数数量
task_signal= q.get() # 从队列中提取参数
Calculator().run(task_signal)
except Exception as e: # 这一步非常重要,如果没有,当进程遇到报错时会提前结束进程
print('error:', e)
pass
else:
pass
def daily_total_update(self):
q = Manager().Queue() # 生成跨进程的队列管理器
for i in task_signal_list: # 将任务参数加入到q中,注意参数必须是str格式
q.put(str(i))
n = multiprocessing.cpu_count() - 1 # 进程数量最大为cpu的核数
pool = Pool(processes=n) # 创建进程池
for i in range(n):
pool.apply_async(self.task_function, args=(q,)) # 向进程池发放任务以及他们共享的参数队列
pool.close()
pool.join() # 主进程等待所有的子进程完成任务
return 'success'
【注释】
- 要使用多进程
pool = Pool(processes=n)
,必须使用q = Manager().Queue()
。这是因为Manager().Queue()
是跨进程的队列管理器,pool中的多个进程都会从Manager().Queue()
提取任务信息。 - 在使用tornado或者flask时,必须使用类封装多进程,否则多进程会开启多个tornado或者flask的服务
- 子进程一定要对报错的信息进行处理,否则子进程中的任务一旦报错就会结束进程,导致后续的任务没有进程去完成。