【问题标题】:Occasional deadlock in multiprocessing.Poolmultiprocessing.Pool 中偶尔出现死锁
【发布时间】:2021-03-14 19:49:37
【问题描述】:

我有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。 它表示池不会获取新任务和/或工作进程卡在队列/管道recv() 调用中。我无法确定地重现这个,改变我的实验的不同配置。如果我再次运行相同的代码,它可能会优雅地完成。

进一步观察:

  • x64 Linux 上的 Python 3.7.9
  • 多处理的启动方法是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 调用完成了吗?这将阻塞直到子进程完成,所以如果它没有......
  • 是的,subprocess.run 完成。当死锁发生时,没有其他工作子进程被报告(例如在htop) - 都处于可中断睡眠状态。
  • 您使用的是什么操作系统和 Python 版本?默认启动方法因各种选项而异(在明显的嫌疑人中,在 3.8 之前,macOS 使用 fork,但 it caused problems,所以 3.8 and higher switched to spawn by default),并且可能与您的问题有关。即使在分叉系统上,您也可能想要更改启动方法; fork 如果父级很大并且子级执行垃圾收集(导致写入时复制,导致内存使用高峰),则可能会出现问题。
  • 你可以尝试将进程限制为 1,看看它是否仍然与大型任务列表死锁?
  • 如果 not 使用 maxtasksperchild 解决了您的问题,您可以尝试升级到 Python 3.8,工作人员管理方面发生了变化。

标签: python multiprocessing deadlock


【解决方案1】:

死锁是由于worker中的高内存使用而发生的,因此触发了OOM杀手,它突然终止了worker子进程,使池处于混乱状态。

This script 重现了我原来的问题。

目前我正在考虑切换到ProcessPoolExecutor,当工作人员突然终止时,这将引发BrokenProcessPool 异常。

参考资料:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-08
    • 1970-01-01
    • 1970-01-01
    • 2013-05-30
    • 2011-07-07
    • 2017-05-27
    • 2012-06-08
    相关资源
    最近更新 更多