在Python中从线程调用多处理安全吗?
问题内容:
根据
https://github.com/joblib/joblib/issues/180,有没有一种安全的方法可以从python中的线程创建子进程?
Python多处理模块不允许在线程内使用。这是真的?
我的理解是,只要您不持有线程即可很好地从线程派生,而在执行时锁定(在当前线程中?在进程中的任何地方?)但是,Python的文档没有说明在fork之后是否安全共享threading.Lock对象。
还有一个问题:从日志记录模块共享的锁会导致fork问题。https://bugs.python.org/issue6721
我不确定这个问题是怎么产生的。听起来好像当前线程派生时,该进程中所有锁的状态都已复制到子进程中(这似乎是设计错误,并且肯定会死锁)。如果是这样,使用多重处理是否真的可以提供任何保护(因为我可以自由创建我的多重处理。在线程化之后创建池。其他线程创建并输入了锁,并且在线程启动后使用了非叉安全日志记录模块)-多处理模块文档也没有对multiprocessing.Pool应该分配在Locks之前保持沉默。
是否可以在各处用multiprocessing.Lock替换threading.Lock来避免此问题,并允许我们安全地组合线程和fork?
问题答案:
听起来好像当前线程派生时,该进程中所有锁的状态都已复制到子进程中(这似乎是设计错误,并且肯定会死锁)。
这不是设计错误,而是fork()
早于单进程多线程。所有锁的状态都被复制到子进程中,因为它们只是内存中的对象。进程的整个地址空间都按fork复制。仅有一些不好的选择:要么通过fork复制
所有 线程,要么拒绝多线程应用程序中的派生。
因此,fork()
除非在子进程之后execve()
或exit()
在子进程中进行操作,否则永远都不是安全的事情。
是否可以在各处用multiprocessing.Lock替换threading.Lock来避免此问题,并允许我们安全地组合线程和fork?
否。 没有什么可以安全地组合线程和派生的,这是无法做到的。
问题是,当一个进程中有多个线程时,在fork()
系统调用之后,您将无法继续在POSIX系统中安全地运行该程序。
例如,Linux手册fork(2)
:
* 在
fork(2)
进入多线程程序之后,子级只能安全地调用异步信号安全函数(请参阅参考资料signal(7)
),直到它调用为止execve(2)
。
也就是说,可以fork()
在多线程程序中然后仅调用异步信号安全的 C 函数(这是 C
函数的相当有限的子集),直到子进程已被另一个可执行文件替换!
子进程中的不安全C函数调用例如
malloc
用于动态内存分配<stdio.h>
格式化输入的任何功能pthread_*
线程状态处理所需的大多数功能,包括创建新线程…
因此,子进程实际上可以安全地执行的操作很少。不幸的是,CPython核心开发人员一直低估了由此引起的问题。甚至现在文档说:
请注意,安全地分叉多线程进程是 有问题的 。
委婉的说法是“不可能”。
如果您 不 使用start方法,则可以安全地 从 具有多个控制线程的Python进程中使用多重处理。在Python
3.4+中,现在可以更改start方法。在包括所有Python
2在内的早期Python版本中,POSIX系统始终表现为被指定为启动方法。这将导致不确定的行为。
__fork
fork
问题不仅限于threading.Lock
对象,还包括C标准库,C扩展等持有的 所有 锁。更糟糕的是,大多数时候人们会说“它对 我有用 ”
…直到它停止工作。
甚至在某些情况下,看似单线程的Python程序实际上在MacOS X中是多线程的,在使用多处理时会导致失败和死锁。
另一个问题是,所有打开的文件句柄,它们的使用,共享套接字在派生的程序中可能表现得很奇怪,但是即使在单线程程序中也是如此。
TL; DR:multiprocessing
在多线程程序中使用,带有C扩展名,打开的套接字等:
- 如果您明确指定不是的启动方法,则在3.4+和POSIX中可以使用
fork
, - 在Windows中很好,因为它不支持分支;
- 在POSIX上的Python 2-3.3中:您几乎会用脚射击。