前言:本博文主要讲解Python并发编程中的进程池(Pool)。
友情链接:
- Python多进程中的multiprocessing
- Python多进程中的fork
- Python进程通信之Queue
文章目录
- 一、进程池Pool
- 二、apply堵塞式
- 三、apply、apply_async的区别
- 3.1 apply阻塞式
- 3.2 apply_async异步非阻塞式
一、进程池Pool
当我们需要创建的子进程数量不多时,可以直接利用multiprocessing
中的Process
动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing
模块提供的Pool
方法。
初始化Pool
时,可以指定一个最大进程数(如果没有指定,则按无限大处理。此时,在生成子进程时会高速处理、程序也在切换,会损耗性能),当有新的请求提交到Pool
中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行,请看下面的实例:
from multiprocessing import Pool
import time, os, random
def test1(msg):
t_start = time.time()
print("%s开始执行,进程号为%d" % (msg, os.getpid()))
time.sleep(random.random() * 2)
t_stop = time.time()
print("%s执行完成,耗时%.2f" % (msg, t_stop - t_start))
if __name__ == "__main__":
# 定义一个进程池,最大进程数2
po = Pool(2)
for i in range(1, 6):
# Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
# 每次循环将会用空闲出来的子进程去调用目标
po.apply_async(test1, (i,))
print("-----start-----")
po.close() # 关闭进程池,关闭后po不再接收新的请求
po.join() # 等待po中所有子进程执行完成,必须放在close语句之后
print("-----end-----")
multiprocessing.Pool常用函数解析:
- apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
- apply(func[, args[, kwds]]):使用阻塞方式调用func
- close():关闭Pool,使其不再接受新的任务;
- terminate():不管任务是否完成,立即终止;
- join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用。
二、apply堵塞式
from multiprocessing import Pool
import time, os, random
def test1(msg):
t_start = time.time()
print("%s开始执行,进程号为%d" % (msg, os.getpid()))
time.sleep(random.random() * 2)
t_stop = time.time()
print("%s执行完成,耗时%.2f" % (msg, t_stop - t_start))
if __name__ == "__main__":
# 定义一个进程池,最大进程数2
po = Pool(2)
for i in range(1, 6):
# Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
# 每次循环将会用空闲出来的子进程去调用目标
po.apply(test1, (i,))
print("-----start-----")
po.close() # 关闭进程池,关闭后po不再接收新的请求
po.join() # 等待po中所有子进程执行完成,必须放在close语句之后
print("-----end-----")
总结: 我们使用 apply
时,进程池产生了堵塞。上述程序,它先执行完上一个再执行下一个,此时就没有发挥我们多任务、多进程的优势。因此:这块我们应该使用 apply_async
,实现非阻塞异步。
三、apply、apply_async的区别
-
apply()
:阻塞主进程,并且依次按照顺序执行子进程,,等到全部子进程都执行完毕后 ,继续执行apply()
后面主进程的代码。 -
apply_async()
:非阻塞异步的,它不会等待子进程执行完毕,主进程会继续执行,它会根据系统调度来进行进程切换。
那么我们通过第一、二部分的案例,可以看出:使用apply_async()
完成了一个多任务的操作,而apply()
实则是一个单任务,这样就没有体现我们多进程的优势。
我们都知道:进程的切换是操作系统来控制的,抢占式的切换模式。当我们首先运行主进程的时候,cpu运行的很快,那么当我们的主进程就运行完毕,整个程序结束。此时,子进程完全没有机会切换到程序,就已经结束了。
3.1 apply阻塞式
首先主进程开始运行,碰到子进程,操作系统切换到子进程,等待子进程运行结束后,再切换到另外一个子进程,直到所有子进程运行完毕。然后在切换到主进程,运行剩余的部分。此时,我们可以发现,我们的程序其实是阻塞的,它一次只能执行一个进程,且执行完毕才会到下一个进程,完全没有利用我们多进程的优势。
3.2 apply_async异步非阻塞式
首先主进程开始运行,碰到子进程后,主进程说:让我先运行个够,等到操作系统进行进程切换的时候,再交给子进程运行。因为我们的程序太短,然而还没等到操作系统进行进程切换,主进程就运行完毕了。想要子进程执行,就告诉主进程:你等着所有子进程执行完毕后,再运行剩余部分。
那么此时,我们需要以下两行代码:
pool.close()
pool.join()
注意: 我们先要close()
一下子进程池, 而且要先在join()
前运行它。
那么此时,因为apply_async
是异步非阻塞式,不用等待当前进程执行完毕,随时跟进操作系统调度来进行进程切换。上一个进程没有执行完,就切换到下一个进程,然后再切换下去(最大不超过进程池规定的进程数),等待所有子进程运行完毕后,最后切换回主进程,执行剩余部分。
因此Python官方也建议我们:废弃 apply()
,使用 apply_async()
。