问题
来自multiprocessing.Pool
docs:
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()
结果
docs(案例1)中的示例确认仅运行了一个工作程序。在接下来的情况下,我们通过应用blocking_func
扩展此示例,该操作会稍有延迟。
注释time.sleep()
中的blocking_func()
行会使所有情况都达成一致。
# 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()
都会创建一个新的进程池,这就是为什么新的进程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个内核。
答案 0 :(得分:2)
您的困惑似乎来自于认为[pool.apply_async(...) for i in range(10)]
是 一个 电话,而实际上有十个独立电话。调用任何池方法都是一种“工作”。一项工作通常可以导致分配一个或多个任务。 apply
方法始终只能在后台执行单个任务。任务是一个不可分割的工作单元,将由一个随机的池工作人员整体接收。
只有一个共享的inqueue
,所有工作人员都受够了。哪个空闲工作者将从等待到get()
的队列中醒来,取决于操作系统。情况1的结果熵仍然有些令人惊讶,并且可能非常幸运,至少除非您确认只有两个核心。
是的,您对运行的观察也受任务所需的计算时间的影响,因为线程(进程中的预定执行单元)通常使用时间切片策略进行调度(例如,对于Windows为约20ms)。 / p>
答案 1 :(得分:1)
该呼叫仅使用一名工人。单个apply_async
不能在两个工作线程中执行。这不会阻止在不同的工作程序中执行多个apply_async
调用。这样的限制将完全与拥有进程池完全相反。
答案 2 :(得分:0)
在@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次迭代(未显示)时会比较密集。