前言:本博文主要讲解Python并发编程中的进程池(Pool)。

友情链接:

  1. Python多进程中的multiprocessing
  2. Python多进程中的fork
  3. 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-----")

Python进程池句柄给子进程 python进程池pool_Python进程池句柄给子进程


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-----")

Python进程池句柄给子进程 python进程池pool_Python进程池句柄给子进程_02


总结: 我们使用 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()