Process Class
使用 Python 模块的 Process
类创建子进程:
def start_string(len, output):
""" Each sub process do"""
str_list = list()
# "abcdefghijklmnopqrstuvwxyz"
str_range = string.ascii_lowercase
for i in range(len):
str_list.append(random.choice(str_range))
gen_str = "-".join(str_list) + "-" + str(os.getpid())
print(gen_str)
time.sleep(1)
# add to shared Queue
output.put(gen_str)
def use_process_main():
# Define an output queue
output = multiprocessing.Queue()
mp_list = list()
for _ in range(4):
p = multiprocessing.Process(target=start_string, args=(5, output))
mp_list.append(p)
# Run all process
for mp in mp_list:
mp.start()
# Block main process util subprocess finish
for mp in mp_list:
mp.join()
print("============ main code ===========")
# Get process results from the output queue
results = [output.get() for p in mp_list]
print("Queue Result:", results)
运行结果:
y-u-b-o-e-26373
b-x-y-i-v-26374
o-a-e-p-d-26375
s-c-t-x-j-26376
============ main code ===========
Queue Result: ['y-u-b-o-e-26373', 'o-a-e-p-d-26375', 'b-x-y-i-v-26374', 's-c-t-x-j-26376']
其中 mp.join() 的作用是阻塞主进程,加上 for 循环,就是等待所有子进程执行完,才开始执行for循环下面的代码,也就是看到的 main code
的 print。
把 for 循环的 join 注释掉结果是什么呢 ?,可以看到下面的结果中,main code
的 pring 信息提前了,并没有等所有子进程结束才开始执行。说明此时主进程和4个子进程是并行执行的。
u-k-o-f-h-26640
t-x-l-m-d-26641
============ main code ===========
n-z-o-b-z-26642
c-v-e-g-j-26643
Queue Result: ['u-k-o-f-h-26640', 't-x-l-m-d-26641', 'n-z-o-b-z-26642', 'c-v-e-g-j-26643']
为什么 mp.join() 没有和 mp.start() 在一个 for 里,而是单独一个 for 循环执行 join 操作。可以试下把 mp.start 和 mp.join 放在一个 for 里是什么结果 ?
# Run all process
for mp in mp_list:
mp.start()
mp.join()
结果如下:
x-w-w-m-u-26906
q-d-q-t-r-26908
y-z-u-g-v-26910
d-i-t-v-f-26912
============ main code ===========
Queue Result: ['x-w-w-m-u-26906', 'q-d-q-t-r-26908', 'y-z-u-g-v-26910', 'd-i-t-v-f-26912']
由于 join 的目的是阻塞主进程,所以放到一个 for 循环里就变成了顺序开启子进程,且启动一个进程后,就阻塞在那里,直到当前子进程执行完,然后开启下一个子进程并阻塞。这个效果其实和单进程是一样的,因为4个进程并没有并行执行,而变成了顺序执行。
Queue中结果的顺序
有时候我们想指定多进程执行的结果,比如for循环先开启子进程1,后开启子进程2,我们希望子进程1的结果先放到Queue中。但是实际执行的时候呢,几个子进程是并行执行的,谁先执行完谁就把结果放到Queue中,所以最后我们拿到的 Queue result结果是随机的,比如下面这个结果
y-u-b-o-e-26373
b-x-y-i-v-26374
o-a-e-p-d-26375
s-c-t-x-j-26376
============ main code ===========
Queue Result: ['y-u-b-o-e-26373', 'o-a-e-p-d-26375', 'b-x-y-i-v-26374', 's-c-t-x-j-26376']
可以看到,进程执行的顺序是 26373, 26374, 26375, 26376
, 子进程在放到 Queue 前有 sleep(1),所以最终执行 put 动作的时候是随机的,最后取到的结果是 ['y-u-b-o-e-26373', 'o-a-e-p-d-26375', 'b-x-y-i-v-26374', 's-c-t-x-j-26376']
。那么如何按照我们要求的顺序返回结果呢?有一种简单的思路就是,在 put result 的时候,加入一个 order index, 如 put 一个 tuple(idx, result),这样在取出 Queue result 的时候,尽管 tuple 的顺序是不可控的,但是我们可以根据 tuple 里的 idx 进行排序,进而得到我们想要的顺序。
Pool Class
使用 multiprocessing.Process()
对象来创建子进程有几个问题:
- 我们到底需要创建几个 Process ?
- 执行完的子进程就被销毁了,能否接着做其他任务 ?
- 如何确保返回顺序 ?
- 频繁创建创建,会带来一定的 OS 开销,如何避免 ?
上述几个问题可以通过 multiprocessing.Pool
对象来解决,创建进程池的时候可以指定池中 processor 的个数,如 pool = multiprocessing.Pool(processes=3)
就是创建含义3个子进程的进程池,当进程池创建好后,可以向进程池中提交任务,池子中的三个进程谁空闲了,就会做下一个等待任务,这样可以避免频繁创建进程带来的开销。
Pool 对象主要有 4 个方法:
- apply
- apply_async
- map
- map_async
其中 pool.apply
和 pool.map
和 Python 内建的 apply, map 是一个意思,它们都可以阻塞主进程,知道所有的子进程结束,并且可以顺序得到子进程的执行结果,具体看下面的例子:
- pool.apply
def squre(x):
res = x**2
print("sub process: ", os.getpid(), x)
return res
def use_pool_apply_main():
# Define pool of 3 processors
pool = multiprocessing.Pool(processes=3)
results = list()
for i in range(6):
res = pool.apply(squre, args=(i,))
results.append(res)
print("============ main code ===========")
print("main result: ", results)
结果是:
sub process: 3829 0
sub process: 3830 1
sub process: 3831 2
sub process: 3829 3
sub process: 3830 4
sub process: 3831 5
============ main code ===========
main result: [0, 1, 4, 9, 16, 25]
- pool.map
def use_pool_map_main():
# Define pool of 3 processors
pool = multiprocessing.Pool(processes=3)
results = pool.map(squre, range(6))
print("============ main code ===========")
print("main result: ", results)
结果是:
sub process: 4094 0
sub process: 4095 1
sub process: 4096 2
sub process: 4094 3
sub process: 4095 4
sub process: 4094 5
============ main code ===========
main result: [0, 1, 4, 9, 16, 25]
map 接收的是一个可迭代的 iterable 对象,返回的也是一个可迭代的结构,包含每个子进程的结果。
- pool.apply_async
async 变量表明这是异步的方法,它会一次开启所有子进程,并且不会阻塞主进程。需要注意的是,它返回的是一个 object, multiprocessing.pool.ApplyResult
对象,需要调用 .get
方法显示结果。
def use_pool_apply_async_main():
# Define pool of 3 processors
pool = multiprocessing.Pool(processes=3)
results = list()
for i in range(6):
# 返回的是一个对象,multiprocessing.pool.ApplyResul
res = pool.apply_async(squre, args=(i,))
results.append(res)
print("============ main code ===========")
print("main result: ", [p.get() for p in results])
结果是:
============ main code ===========
sub process: 5418 2
sub process: 5417 1
sub process: 5416 0
sub process: 5418 3
sub process: 5416 4
sub process: 5417 5
main result: [0, 1, 4, 9, 16, 25]
因为没有阻塞主进程,可以看到 main code
的 print 信息时先输出的,要想实现阻塞主进程,需要调用 pool.join() 方法:
def use_pool_apply_async_main():
# Define pool of 3 processors
pool = multiprocessing.Pool(processes=3)
results = list()
for i in range(6):
# 返回的是一个对象,multiprocessing.pool.ApplyResul
res = pool.apply_async(squre, args=(i,))
results.append(res)
# async 变量
pool.close()
pool.join()
print("============ main code ===========")
print("main result: ", [p.get() for p in results])
- pool.map_async
和 map 一样,接收一个可迭代对象作为参数,返回的不再是迭代对象,而是一个封装过的 MapResult
对象,需要调用 .get
来输出全部内容:
def use_pool_apply_map_main():
# Define pool of 3 processors
pool = multiprocessing.Pool(processes=3)
results = pool.map_async(squre, range(6))
print("============ main code ===========")
print("main result: ", results.get())
和 apply_async 一样,没有阻塞主进程,如果想实现阻塞,同样需要调用 pool.join()
方法