[英]multiprocessing.Pool: When to use apply, apply_async or map?
[英]why is more than one worker used in `multiprocessing.Pool().apply_async()`?
问题
从multiprocessing.Pool
文档 :
apply_async(func ...)
:apply()
方法的一种变体,它返回一个结果对象。 ...
进一步阅读...
apply(func[, args[, kwds]])
:使用参数args和关键字参数kwds调用func。 它会阻塞直到结果准备就绪。 给定此块,apply_async()更适合于并行执行工作。 此外,func仅在池的工作程序之一中执行。
最后的粗线表明只使用了池中的一个工人。 我发现这仅在某些条件下是正确的。
特定
这是在三种类似情况下执行Pool.apply_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
扩展此示例,该操作会延迟一段时间。
在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_async()
调用apply_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_async()
为什么人总比一个人时使用多个time.sleep()
是取消注释? 阻塞是否应该甚至影响apply
或apply_async
使用的工作程序数量?
注意:先前的相关问题会问“为什么只雇用一名工人?” 这个问题提出了相反的问题-“为什么不只雇用一个工人?” 我在Windows计算机上使用2个内核。
您的困惑似乎来自于认为[pool.apply_async(...) for i in range(10)]
是一个调用,而实际上有十个独立调用。 调用任何池方法都是一种“工作”。 一项工作通常可以导致分配一个或多个任务。 apply
-方法总是在引擎盖下只生产单一的任务。 任务是一个不可分割的工作单元,将由一个随机的池工作人员整体接收。
只有一个共享inqueue
,所有工作人员都受够了。 哪个空闲工作者将从等待队列中的get()
唤醒,取决于操作系统。 情况1的结果熵仍然有些令人惊讶,并且可能非常幸运,至少除非您确认只有两个核心。
是的,您对运行的观察也受任务所需的计算时间的影响,因为线程(进程中的计划执行单元)通常使用时间切片策略进行计划(例如,对于Windows为〜20ms)。
该呼叫仅使用一名工人。 单个apply_async
不能在两个工作apply_async
中执行。 这不会阻止在不同的工作程序中执行多个apply_async
调用。 这样的限制将完全与拥有进程池的点背道而驰。
在@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)
if __name__ == '__main__':
start = time.time()
apply_async()
end = time.time()
print(f"Duration : {end - start:.2f}s")
演示
将大整数( 100000
)传递给offset参数,例如...[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
有趣的是,在不到2秒的时间内,异步计算了10万斐波纳契数10次。 相比之下,使用Fibonacci的递归实现在大约30次迭代(未显示)时会比较密集。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.