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()
调用中。我无法确定地重现这一点,改变了我实验的不同配置。如果我再次 运行 相同的代码,它可能会正常完成。
进一步观察:
- Python x64 上的 3.7.9 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
死锁是由于 worker 的高内存使用率导致的,从而触发了 OOM killer,它突然终止了 worker 子进程,使池处于 混乱 状态。
This script 重现我原来的问题。
目前我正在考虑切换到 ProcessPoolExecutor
,当工作人员突然终止时会抛出 BrokenProcessPool
异常。
参考文献:
我有 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()
调用中。我无法确定地重现这一点,改变了我实验的不同配置。如果我再次 运行 相同的代码,它可能会正常完成。
进一步观察:
- Python x64 上的 3.7.9 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
死锁是由于 worker 的高内存使用率导致的,从而触发了 OOM killer,它突然终止了 worker 子进程,使池处于 混乱 状态。
This script 重现我原来的问题。
目前我正在考虑切换到 ProcessPoolExecutor
,当工作人员突然终止时会抛出 BrokenProcessPool
异常。
参考文献: