了解了Python的多进程之后,我们会发现,如果有大量任务需要使用多进程来完成,则可能需要频繁的创建删除进程,给计算机带来较多的资源消耗。(多进程的缺点就是会频繁的创建和删除进程,消耗计算机资源)。基于这种情况,于是出现了进程池。
进程池的原理:
- 创建适当的进程放入进程池,用来处理待处理事件,处理完毕后进程不销毁,仍然在进程池中等待处理其他事件。进程的复用降低了资源的消耗。
举一个通俗易懂的例子:
- 你的工厂有100个零件需要处理,需要请零时工来处理,如果直接使用多进程,那么就需要请100个工人来同时处理,这样100个人处理确实非常快,但是由于请的人太多了,导致成本太高,而且你在请这100个人的时候,也需要一个一个的打电话通知,然后处理完后,你也需要一个一个的给别人结算工资,这样花费很多时间,同时使得你疲惫不堪。而如果使用进程池,那么你就只需要请10个人,这10个人同时处理,每个人处理完一个零件后就去拿下一个零件,直到所有的零件处理完毕。这样在处理零件的时间上虽然可能没有100个人同时处理快,但是使得花费时间和花费成本相对平衡,使你能获得最大程度上的收益。
使用进程池步骤:
- 创建进程池,在池内放入适当的进程
- 将事件加入到进程池等待队列
- 不断取进程执行事件,直到所有事件执行完毕
- 关闭进程池,回收进程
代码如下:
from multiprocessing import Pool
from time import sleep
from datetime import datetime
def test(msg):
sleep(3)
print(msg, ':', datetime.now().strftime('%H:%M:%S'))
#创建进程池
pool = Pool(processes = 3)
for i in range(1, 10):
msg = "No."+str(i)
print(msg, "放入队列时间:", datetime.now().strftime('%H:%M:%S'))
#将事件放入进程池队列,等待执行
pool.apply_async(func = test,args = (msg,))
#关闭进程池
pool.close()
#回收进程池
pool.join()
在代码中,我设置进程池中有三个进程,然后通过循环遍历的方式向进程池的等待队列中加入需要处理的任务(9个任务),然后进程池会从队列中获取任务然后处理。进程池一次最多只能处理3个任务
运行结果:
gk@gk-vm:~/python$ python3 pool_test.py
No.1 放入队列时间: 15:16:03
No.2 放入队列时间: 15:16:03
No.3 放入队列时间: 15:16:03
No.4 放入队列时间: 15:16:03
No.5 放入队列时间: 15:16:03
No.6 放入队列时间: 15:16:03
No.7 放入队列时间: 15:16:03
No.8 放入队列时间: 15:16:03
No.9 放入队列时间: 15:16:03
No.1 : 15:16:06
No.2 : 15:16:06
No.3 : 15:16:06
No.5 : 15:16:09
No.4 : 15:16:09
No.6 : 15:16:09
No.7 : 15:16:12
No.9 : 15:16:12
No.8 : 15:16:12
观察运行结果中的时间,所有的任务几乎在同一时间全部放入了进程池等待队列中,然后,在执行的任务结果中,每三个输出结果为一组,时间间隔为3秒。
其中:
Pool(processes)
- 功能 : 创建进程池对象
- 参数(processes) :表示设置进程池中有多少进程
pool.close()
- 功能: 关闭进程池
pool.join()
- 功能:回收进程池
pool.apply_async(func,args,kwds)
- 功能 : 将事件放入到进程池队列
- 参数 : func(事件函数),args(以元组形式给func传参),kwds(以字典形式给func传参)
- 返回值 : 返回一个代表进程池事件的对象
特别注意:
pool.apply_async(func,args,kwds)是通过异步非阻塞的方式将任务加入到进程池的等待队列中,即在上面的代码中for循环不会出现阻塞的情况,所以,所有的任务几乎在同一时间就全部进入了进程池的等待队列中。
还有一种pool.apply(func,args,kwds)的方式将任务放到进程池等待队列中,但是,这是阻塞的,即:它会等待进程池将上一个任务处理完成后才会将下一个任务放入进程池等待队列中,即上面的代码中,for循环是阻塞的。
更改上面的代码:
from multiprocessing import Pool
from time import sleep
from datetime import datetime
def test(msg):
sleep(3)
print(msg, ':', datetime.now().strftime('%H:%M:%S'))
#创建进程池
pool = Pool(processes = 3)
for i in range(1, 10):
msg = "No."+str(i)
print(msg, "放入队列时间:", datetime.now().strftime('%H:%M:%S'))
#将事件放入进程池队列,等待执行
pool.apply(func = test,args = (msg,))
#关闭进程池
pool.close()
#回收进程池
pool.join()
运行结果:
gk@gk-vm:~/python$ python3 pool_test3.py
No.1 放入队列时间: 15:13:21
No.1 : 15:13:24
No.2 放入队列时间: 15:13:24
No.2 : 15:13:27
No.3 放入队列时间: 15:13:27
No.3 : 15:13:30
No.4 放入队列时间: 15:13:30
No.4 : 15:13:33
No.5 放入队列时间: 15:13:33
No.5 : 15:13:36
No.6 放入队列时间: 15:13:36
No.6 : 15:13:39
No.7 放入队列时间: 15:13:39
No.7 : 15:13:42
No.8 放入队列时间: 15:13:42
No.8 : 15:13:45
No.9 放入队列时间: 15:13:45
No.9 : 15:13:48
通过运行结果我们可以看到,在上一个任务执行完了之后才会将下一个任务放入等待队列中。
综上: 建议使用apply_async。
另外: 还可以通过pool.map将任务加入到进程池的等待队列中。
pool.map(func,iter)
- 功能: 将要做的时间放入进程池
- 参数:func(要执行的函数),iter(迭代对象)
- 返回值 : 返回事件函数的返回值列表
修改之前的代码:
from multiprocessing import Pool
from time import sleep
from datetime import datetime
def test(num):
sleep(3)
print(num, ':', datetime.now().strftime('%H:%M:%S'))
return num
#创建进程池
pool = Pool(processes = 3)
li = pool.map(func=test, iterable=range(1, 10))
#关闭进程池
pool.close()
#回收进程池
pool.join()
print("返回值列表:", li)
运行结果:
gk@gk-vm:~/python$ python3 pool_test4.py
1 : 15:49:03
2 : 15:49:03
3 : 15:49:03
4 : 15:49:06
5 : 15:49:06
6 : 15:49:06
7 : 15:49:09
8 : 15:49:09
9 : 15:49:09
返回值列表: [1, 2, 3, 4, 5, 6, 7, 8, 9]