Python将 futures与分叉块结合起来,永远存在
下面是一个最小可复现的示例
import os
import concurrent.futures
import multiprocessing
tm = concurrent.futures.ProcessPoolExecutor(1, multiprocessing.get_context("spawn"))
def foo() -> None:
pipe_r, pipe_w = os.pipe()
if c := os.fork():
os.close(pipe_r)
return
os.close(pipe_w)
os.setsid()
try:
os.read(pipe_r, 1)
finally:
os._exit(0)
foo()
运行时,这个会一直阻塞。如果我移除ProcessPoolExecutor,它可以工作。此外它在3.12.13版会失败,但在3.8版则可工作。显然发生了死锁,但我还在努力理解为什么?
解决方案
问题在于forking一个多线程进程并不安全:锁(以及其他线程状态)可能在拷贝到子进程时处于锁定状态,子进程会一直等待那些在子进程中不再存在的线程。这在你从一个有线程/工作器机制活跃的进程调用 os.fork() 时,死锁就有可能发生(例如并发池、日志线程、库创建的后台线程等)。
“父进程使用os.fork() 来分叉Python解释器。子进程在开始时,实质上与父进程相同。父进程的所有资源都被子进程继承。请注意,对多线程进程安全地fork是有问题的。”
https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods
这解释了为什么移除 ProcessPoolExecutor(它创建了线程/后台状态和工作进程)会让问题消失。在那套机制还存在时进行fork可能会拷贝一个后台线程持有的锁;子进程因此在该锁上永久阻塞。
你可以参考这个。看起来似乎与你遇到的问题类似:
Python multiprocessing pool some process in deadlock when forked but runs when spawned
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。