Python 为什么在“multiprocessing.Pool().apply\u async()”中使用了多个辅助进程?

Python 为什么在“multiprocessing.Pool().apply\u async()”中使用了多个辅助进程?,python,multiprocessing,python-multiprocessing,Python,Multiprocessing,Python Multiprocessing,问题 从多处理.Pool: apply\u async(func…:返回结果对象的apply()方法的变体 进一步阅读 apply(func[,args[,kwds]]):使用参数args和关键字参数kwds调用func。它将阻塞,直到结果就绪。考虑到这些块,apply_async()更适合并行执行工作此外,func仅在池的一个工作线程中执行。 最后一条粗体线表示只使用池中的一个工人。我发现这只有在某些情况下才是正确的 给定的 下面是执行池的代码。在三种类似的情况下应用\u async()。在所

问题

多处理.Pool

apply\u async(func…
返回结果对象的
apply()
方法的变体

进一步阅读

apply(func[,args[,kwds]])
:使用参数args和关键字参数kwds调用func。它将阻塞,直到结果就绪。考虑到这些块,apply_async()更适合并行执行工作此外,func仅在池的一个工作线程中执行。

最后一条粗体线表示只使用池中的一个工人。我发现这只有在某些情况下才是正确的

给定的

下面是执行
池的代码。在三种类似的情况下应用\u async()
。在所有情况下,都会打印进程id

import os
import time
import multiprocessing as mp


def blocking_func(x, delay=0.1):
    """Return a squared argument after some delay."""
    time.sleep(delay)                                  # toggle comment here
    return x*x, os.getpid()


def apply_async():
    """Return a list applying func to one process with a callback."""
    pool = mp.Pool()
    # Case 1: From the docs
    results = [pool.apply_async(os.getpid, ()) for _ in range(10)]
    results = [res.get(timeout=1) for res in results]
    print("Docs    :", results)

    # Case 2: With delay
    results = [pool.apply_async(blocking_func, args=(i,)) for i in range(10)]
    results = [res.get(timeout=1)[1] for res in results]
    print("Delay   :", results)

    # Case 3: Without delay
    results = [pool.apply_async(blocking_func, args=(i, 0)) for i in range(10)]
    results = [res.get(timeout=1)[1] for res in results]
    print("No delay:", results)

    pool.close()
    pool.join()


if __name__ == '__main__':
    apply_async()
结果

来自(案例1)的示例确认仅运行了一个辅助进程。在接下来的案例中,我们将通过应用
阻塞功能
来扩展此示例,该功能会延迟阻塞

注释
blocking_func()
中的
time.sleep()
行会使所有案例都一致

# Time commented
# 1. Docs    : [8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208]
# 2. Delay   : [8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208]
# 3. No delay: [8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208, 8208]
每次调用
apply\u async()
都会创建一个新的进程池,这就是为什么新进程ID与后者不同的原因

# Time uncommented
# 1. Docs    : [6780, 6780, 6780, 6780, 6780, 6780, 6780, 6780, 6780, 6780]
# 2. Delay   : [6780, 2112, 6780, 2112, 6780, 2112, 6780, 2112, 6780, 2112]
# 3. No delay: [6780, 2112, 6780, 2112, 6780, 2112, 6780, 2112, 6780, 2112]
但是,如果未注释
time.sleep()
,即使延迟为零,也会使用多个工作进程

简言之,在未注释的情况下,我们希望在案例1中有一个工人,但在案例2和案例3中有多个工人

问题

虽然我希望
Pool().apply\u async()
只使用一个worker,但为什么在
time.sleep()
未注释时使用多个worker?阻塞是否会影响
apply
apply\u async
使用的工作人员数量


注意:前面的相关问题会问“为什么只使用一个工作进程?”这个问题会问相反的问题——“为什么不使用一个工作进程?”我在Windows机器上使用两个内核。

只有一个工作进程用于该调用。不能在两个辅助进程中执行单个
apply\u async
。这并不阻止在不同的工作进程中执行多个
apply\u async
调用。这样的限制完全违背了拥有一个进程池的观点。

您的困惑似乎来自于思考
[pool.apply_async(…)for i in range(10)]
一个调用,而实际上有十个独立调用。对任何池方法的调用都是“作业”。一个作业通常会导致分配一个或多个任务<代码>应用-方法始终只在引擎盖下生成单个任务。任务是一个不可分割的工作单元,它将作为一个整体由一个随机的池工作者接收

队列中只有一个共享的
,所有工人都被解雇了。哪个空闲工作进程将从等待中唤醒到
get()
该队列中的任务取决于操作系统。案例1的结果仍然有点令人惊讶,可能非常幸运,至少除非你确认你只有两个核


是的,您对这次运行的观察也会受到任务所需计算时间的影响,因为线程(进程中的计划执行单元)通常是通过时间切片策略进行计划的(例如,对于Windows约20毫秒)。

受@Darkonaut评论的刺激,我进一步检查,发现阻塞功能太快。我用一个新的密集阻塞函数测试了修改后的代码

代码

新的阻塞函数迭代计算斐波那契数。可以传入一个可选参数以扩大范围并计算更大的数字

def blocking_func(n, offset=0):
    """Return an intensive result via Fibonacci number."""
    n += offset
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a, os.getpid()


def blocking_func(n, offset=0):
    """Return an intensive result via recursive fibonacci number."""
    func = blocking_func
    n += offset
    if n <= 1:
        return n, os.getpid()
    return func(n-1) + func(n-2)
演示

将一个大整数(
100000
)传递给偏移量参数,例如
…[pool.apply_async(blocking_func,args=(i,100000))…]
并运行代码,我们可以更可靠地触发进程切换

# Results
Docs     : [10032, 10032, 10032, 10032, 10032, 10032, 10032, 10032, 10032, 10032]
Offset   : [10032, 8268, 10032, 8268, 10032, 8268, 10032, 8268, 10032, 8268]
Duration : 1.67s

有趣的是,100k斐波那契数在不到2秒内异步计算了10次。相比之下,使用斐波那契的递归实现在大约30次迭代(未显示)时会相当密集。

谢谢。我不知道每次通话可以使用一个以上的工作人员。为什么在案例1中只使用一个过程?任务是否不够重,不值得使用更多流程?谢谢。不,我知道有十个调用
apply\u async()
。尚不清楚的是,每个调用都属于从工作人员池中随机选择的过程。结果让我相信只有一个进程被使用过,但这可能是一个侥幸,因为,是的,我只有两个内核,而且工作强度可能不足以触发进程切换皮朗8分钟ago@pylang啊,我明白了。这些进程是在实例化池时创建的。所以有两个进程,但在处理作业的瞬间,只需要安排一个进程。
# Results
Docs     : [10032, 10032, 10032, 10032, 10032, 10032, 10032, 10032, 10032, 10032]
Offset   : [10032, 8268, 10032, 8268, 10032, 8268, 10032, 8268, 10032, 8268]
Duration : 1.67s