multiprocessing.Pool 中偶尔出现死锁

Occasional deadlock in multiprocessing.Pool

我有 N 个独立任务,它们在 multiprocessing.Pool 大小 os.cpu_count() 中执行(在我的例子中是 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 时的回溯是 here。 它表示池不会获取新任务 and/or 工作进程卡在队列/管道 recv() 调用中。我无法确定地重现这一点,改变了我实验的不同配置。如果我再次 运行 相同的代码,它可能会正常完成。

进一步观察:


更新: 似乎死锁发生在池大小为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

死锁是由于 worker 的高内存使用率导致的,从而触发了 OOM killer,它突然终止了 worker 子进程,使池处于 混乱 状态。

This script 重现我原来的问题。

目前我正在考虑切换到 ProcessPoolExecutor,当工作人员突然终止时会抛出 BrokenProcessPool 异常。

参考文献: