我有 N
个独立的任务,在大小为 os.cpu_count()
的 multiprocessing.Pool
中执行(在我的情况下是 8),并使用 maxtasksperchild=1
(即每个新任务都创建一个新的工作进程)。
主脚本可以简化为:
import subprocess as sp
import multiprocessing as mp
def do_work(task: dict) -> dict:
res = {}
# ... work ...
for i in range(5):
out = sp.run(cmd, stdout=sp.PIPE, stderr=sp.PIPE, check=False, timeout=60)
res[i] = out.stdout.decode('utf-8')
# ... some more work ...
return res
if __name__ == '__main__':
tasks = load_tasks_from_file(...) # list of dicts
logger = mp.get_logger()
results = []
with mp.Pool(processes=os.cpu_count(), maxtasksperchild=1) as pool:
for i, res in enumerate(pool.imap_unordered(do_work, tasks), start=1):
results.append(res)
logger.info('PROGRESS: %3d/%3d', i, len(tasks))
dump_results_to_file(results)
有时进程池会卡住。当我执行
KeyboardInterrupt
时,出现的回溯信息在这里。
它表明进程池无法获取新任务,和/或工作进程在队列/管道的recv()
调用中卡住了。我无法确定地复现这个问题,在实验中尝试不同的配置后仍然无法复现。如果我再次运行相同的代码,则有机会优雅地完成。
进一步观察:
- Python 3.7.9 在 x64 Linux 上
- 使用
fork
作为多进程的启动方法(使用spawn
不能解决此问题) strace
显示进程被卡在一个futex wait
上;gdb的回溯也显示:do_futex_wait.constprop
- 禁用日志记录/显式刷新并没有帮助
- 任务的定义中没有错误(即它们都可加载)。
更新:即使使用大小为1的进程池也会发生死锁。
strace
报告该进程被阻塞在尝试获取位于 0x564c5dbcd000
处的某个锁上:
futex(0x564c5dbcd000, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 0, NULL, FUTEX_BITSET_MATCH_ANY
并且gdb
确认:
(gdb) bt
#0 0x00007fcb16f5d014 in do_futex_wait.constprop () from /usr/lib/libpthread.so.0
#1 0x00007fcb16f5d118 in __new_sem_wait_slow.constprop.0 () from /usr/lib/libpthread.so.0
#2 0x0000564c5cec4ad9 in PyThread_acquire_lock_timed (lock=0x564c5dbcd000, microseconds=-1, intr_flag=0)
at /tmp/build/80754af9/python_1598874792229/work/Python/thread_pthread.h:372
#3 0x0000564c5ce4d9e2 in _enter_buffered_busy (self=self@entry=0x7fcafe1e7e90)
at /tmp/build/80754af9/python_1598874792229/work/Modules/_io/bufferedio.c:282
#4 0x0000564c5cf50a7e in _io_BufferedWriter_write_impl.isra.2 (self=0x7fcafe1e7e90)
at /tmp/build/80754af9/python_1598874792229/work/Modules/_io/bufferedio.c:1929
#5 _io_BufferedWriter_write (self=0x7fcafe1e7e90, arg=<optimized out>)
at /tmp/build/80754af9/python_1598874792229/work/Modules/_io/clinic/bufferedio.c.h:396
subprocess.run
调用会完成吗?它会一直阻塞直到子进程结束,所以如果没有...... - bnaeckerfork
,但是这引发了问题,所以3.8及更高版本默认切换到spawn
),这可能与你的问题有关。即使在一个分叉系统上,你可能也想改变启动方法;如果父进程很大而子进程执行垃圾回收(导致写时复制,使内存使用量飙升),fork
可能会成为一个问题。 - ShadowRangermaxtasksperchild
无法解决您的问题,您可以尝试升级到Python 3.8,因为有关于工作进程管理的更改。 - Darkonaut