从 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)
:
即可以在多线程程序中 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 上:你会搬起石头砸自己的脚。
根据 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)
:
即可以在多线程程序中 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 上:你会搬起石头砸自己的脚。