概述
创建子进程数量不多时,可以使用Process,但创建 较多进程 时,用multiprocessing.Pool实现更为 简单
进程池
池
特点:(类似小学、初中的数学题,某水池,一头进水,一头出水。。。)
- 水池有 最大容量
- 刚开始时,水池空,进水口进水,一直到 水池满水,不再进水,即使进水口有水,也不进水
- 当需要 大量用水 时,即 出水口大于进水口,如果没有水池,将供不应求。有水池后,在一段时间内,可以 提供大量用水
- 当水池的水不满时,如果 进水口有水 ,就 继续进水
进程池
特点:
- 初始化Pool时,可以指定最大进程数
- 如果进程池 没有满,就会 创建一个新的进程 执行任务
- 如果进程池已满,不再创建 进程,如果有 未执行的任务 则处于 等待
- 当某子进程执行任务结束,会 空闲 出来,不会退出,继续执行后面的任务,直到全部执行结束
注意:
进程池中的子进程全部启动后,不再 创建 新的子进程,而是利用 空闲的子进程 继续 执行 后面的 任务,这样可以 提高执行效率,因为创建进程需要 消耗系统资源
Pool的api介绍
导入模块
from multiprocessing import Pool
初始化进程池
Pool(n)
参数:
- n:最大进程数,可以为空
apply() 几乎不用
添加任务,是 阻塞 的且支持结果返回。
注意:
此方法较少使用,一个任务执行后,才会执行下一个任务,而不是多进程并发执行,没有体现多进程的意义,所以较少使用
语法:
apply(func[, args=()[, kwds={}]])
apply_async()
添加任务,是非阻塞的且支持结果返回(需要调用get()方法),支持进行回调。
注意:
必须调用join()后才会启动进程
语法:
apply_async(func[, args=()[, kwds={}[, callback=None]]])
参数说明:
- func:调用对象
- args:表示调用对象的位置参数元组
- kwargs:表示调用对象的关键字参数字典
map() 较少使用
注意:
此方法较少使用,一个任务执行后,才会执行下一个任务,而不是多进程并发执行,没有体现多进程的意义,所以较少使用
语法:
map(func, iterable[, chunksize=None])
Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程 阻塞 直到结果返回。
注意:虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。
map_async()
语法:
map_async(func, iterable[, chunksize[, callback]])
与map用法一致,但是它是非阻塞的。其有关事项见apply_async。
close()
关闭进程池,不在接受新的任务。
terminal()
结束工作进程,不在处理未处理的任务。
join()
主进程阻塞,等待子进程运行结束后,主进程才继续运行
通过apply_async()方法添加任务,必须调用join()方法才能 启动进程池
注意:
必须放在close()或terminate()之后使用,否则抛 “AssertionError 异常”
get()
获取调用对象的返回值
注意:
在循环添加任务时,不要调用get()方法,否则程序会 串行
Pool的使用
例子
4人的工程队修1000米公路,每人每次修路100米,通过进程池模拟实现
# coding=utf-8
from multiprocessing import Pool
import time,os
def task(num):
print('%s号工人修100米,pid:%s'%(num,os.getpid()))
time.sleep(3)
print('%s号工人修100米完成'%num)
if __name__ == "__main__":
print('4人的工程队修1000米公路')
#初始化进程池,最大进程数时4
p=Pool(4)
#修1000米公路
num=1
length=1000
while length!=0:
#添加任务,第一次可以添加4个任务,之后有空闲进程时,才会继续添加任务
p.apply_async(task,args=(num,))
length-=100
if num==4:
num=1
else:
num+=1
#关闭进程池,不在接受新的任务
p.close()
#主进程阻塞,启动进程池,只会启动4个进程,最多同时执行4个任务,当任务执行结束后,有进程空闲下来,再用空闲的进程执行下一个任务
p.join()
print('修1000米公路完成')
说明:
初始化进程池时,虽然数量时4,但可以添加所有任务,注意,只会启动4个进程 执行全部任务,当某进程执行结束后,空闲下来,再用该空闲的进程执行下一个任务。
注意:
进程池中的子进程全部启动后,不再 创建 新的子进程,而是利用 空闲的子进程 继续 执行 后面的 任务,这样可以 提高执行效率,因为创建进程需要 消耗系统资源
调用对象的返回值
有时执行任务后,要得到返回值,使用Pool可以很方便的实现
#coding=utf-8
from multiprocessing import Pool
import time,os,random
def xiulu(num):
print('%s号工人开始修路,pid=%s'%(num,os.getpid()))
#随机生成3~6的浮点数
t=random.randint(3,6)
#模拟修路的时长
time.sleep(t)
print('%s号工人修路100米完成,耗时:%s,pid=%s'%(num,t,os.getpid()))
return t
if __name__ == "__main__":
print('开始修路')
p=Pool(4)
num=1
length=1000
results=[]
while length!=0:
result=p.apply_async(func=xiulu,args=(num,))
#此处不能用print()直接打印结果,因为get()方法会导致串行
# print(result.get())
results.append(result)
if num==4:
num=1
else:
num+=1
length-=100
p.close()
p.join()
for item in results:
print(item.get())
print('修路1000米完成')
例子(废弃)
#coding=utf-8
from multiprocessing import Pool
import os
import time
#假设该函数是耗时代码
def task(*args):
print('子进程-%d 开始,pid=%d,ppid=%d'%(args[0],os.getpid(),os.getppid()))
time.sleep(args[0])
if __name__ == "__main__":
print('主进程pid=%d'%os.getpid())
#初始化进程池,最大进程数是3
p=Pool(3)
for index in range(8):
#添加任务,虽然进程池最大进程数时3,但可以添加所有任务
p.apply_async(task,args=(index,))
#关闭进程池,不在接受新的任务
p.close()
#主进程阻塞,启动进程池,只会启动3个进程执行3个任务,当任务执行后,有进程空闲下来,再用空闲的进程执行下一个任务
p.join()
print('主进程结束')
说明:
初始化进程池时,虽然数量时3,但可以添加所有任务,注意,只会启动3个进程 执行3个任务,当任务执行后,有进程空闲下来,再用空闲的进程执行下一个任务
注意:
没有再次创建进程
类比:
相当于饮水机,只有两个水龙头,可以给n个杯子灌水,但同时只给2个杯子灌水,灌满后,再用空闲的水龙头给下一个杯子灌水,而不是再添加一个水龙头
调用对象的返回值(废弃)
有时执行任务后,要得到返回值,使用Pool可以很方便的实现
#coding=utf-8
from multiprocessing import Pool
import os
import time
#假设该函数是耗时代码
def task(*args):
print('子进程-%d 开始,pid=%d,ppid=%d'%(args[0],os.getpid(),os.getppid()))
num=0
for item in range(30000000):
num+=1
return num
if __name__ == "__main__":
starttime=time.time()
print('主进程pid=%d'%os.getpid())
#定义list,存放返回的结果
results=[]
#初始化进程池,最大进程数时3
p=Pool(3)
for index in range(8):
#添加任务,可以添加所有任务
r=p.apply_async(task,(index,))
results.append(r)
#如果直接打印结果,虽然创建多个进程,但其实是一个进程运行结束后,才运行下一个进程
# print(r.get())
#关闭进程池,不在接受新的任务
p.close()
#主进程阻塞,启动进程,只会启动3个进程执行3个任务,当任务执行后,有进程空闲下来,再用空闲的进程执行下一个任务
p.join()
print('主进程结束')
for item in results:
print(item.get())
endtime=time.time()
print('耗时:%d'%(endtime-starttime))
进程池的初始数量
进程池的初始数量,与硬件有关,如cpu数量,cpu核心数量,内存大小等,所以具体设置成多少,需要具体情况具体分析,并且需要反复测试运行效率,最终得到合理的数量



