从 Python 中的线程调用多处理安全吗?

Safe to call multiprocessing from a thread in Python?

根据 https://github.com/joblib/joblib/issues/180, and Is there a safe way to create a subprocess from a thread in python? Python 多处理模块不允许在线程内使用。这是真的吗?

我的理解是从线程中分叉是可以的,只要你 当您这样做时(在当前线程中?进程中的任何地方?)没有持有 threading.Lock。但是,Python 的 documentation 没有说明 threading.Lock 对象在分叉后是否安全共享。

还有这个:从日志记录模块共享的锁会导致 fork 出现问题。 https://bugs.python.org/issue6721

我不确定这个问题是怎么产生的。听起来当当前线程分叉时,进程中任何锁的状态都被复制到子进程中(这似乎是一个设计错误并且肯定会死锁)。如果是这样,使用 multiprocessing 是否真的提供任何保护(因为我可以在其他线程创建和输入 threading.Lock 之后自由创建我的 multiprocessing.Pool,并且在线程启动后使用 not- fork-safe logging module) -- multiprocessing module docs 也没有说明 multiprocessing.Pools 是否应该在 Locks 之前分配。

将 threading.Lock 替换为 multiprocessing.Lock 是否可以避免这个问题并允许我们安全地组合线程和分支?

It sounds like the state of any locks in the process are copied into the child process when the current thread forks (which seems like a design error and certain to deadlock).

这不是设计错误,而是fork()早于单进程多线程。所有锁的状态都被复制到子进程中,因为它们只是内存中的对象;进程的整个地址 - space 被复制为 fork 中的原样。只有不好的选择:要么复制 所有 线程进行分叉,要么在多线程应用程序中拒绝分叉。

因此,在多线程程序中执行 fork() 从来都不是安全的事情,除非随后在子进程中执行 execve()exit()

Does replacing threading.Lock with multiprocessing.Lock everywhere avoid this issue and allow us to safely combine threads and forks?

不。没有什么可以安全地结合线程和分叉,这是不可能的。


问题是,当您在一个进程中有多个线程时,在 fork() 系统调用之后,您无法安全地继续 运行 POSIX 系统中的程序。

例如,Linux 手册 fork(2):

  • After a fork(2) in a multithreaded program, the child can safely call only async-signal-safe functions (see signal(7)) until such time as it calls execve(2).

即可以在多线程程序中 fork() 然后只调用异步信号安全 C 函数(这是 C 函数的一个相当有限的子集),直到子进程已被另一个可执行文件替换!

例如子进程中的不安全 C 函数调用

  • malloc 用于动态内存分配
  • 格式化输入的任何<stdio.h>函数
  • 线程状态处理所需的大多数 pthread_* 函数,包括创建新线程...

因此子进程实际上可以安全地做的事情很少。不幸的是,CPython 核心开发人员一直在淡化由此引起的问题。即使现在 documentation 说:

Note that safely forking a multithreaded process is problematic.

"impossible" 的委婉说法。


如果您不是[=80=,那么从具有多个控制线程的Python进程使用多进程是安全的 ] 使用fork启动方法;在 Python 3.4+ 中是 now possible to change the start method。在以前的 Python 版本中,包括所有 Python 2,POSIX 系统总是表现得好像 fork 被指定为启动方法;这将导致未定义的行为。

问题不仅限于 threading.Lock 个对象,还有 所有 由 C 标准库、C 扩展等持有的锁。更糟糕的是,大多数时间人们会说 "it works for me"... 直到它停止工作。

甚至出现过看似单线程的 Python 程序在 MacOS X 中实际上是多线程的情况,导致在使用多进程时出现故障和死锁。

另一个问题是所有打开的文件句柄、它们的使用、共享套接字在分叉的程序中可能表现得很奇怪,但即使在单线程程序中也是如此。

TL;DR:在多线程程序中使用 multiprocessing,使用 C 扩展,打开套接字等:

  • 在 3.4+ 和 POSIX 中很好,如果您明确指定不是 fork
  • 的启动方法
  • 在 Windows 中很好,因为它不支持分叉;
  • 在 Python 2 - 3.3 在 POSIX 上:你会搬起石头砸自己的脚。