问题描述
根据 https://github.com/joblib/joblib/issues/180和Is there a safe way to create a subprocess from a thread in python? Python多处理模块不允许从线程内部使用。这是真的吗?
我的理解是,从线上叉出来是好的,只要你 没有持有线程。当您持有线程时锁定(在当前线程中?在这个过程中的任何地方?)。然而,在派生之后是否安全地共享threading.Lock对象这一问题上,Python的documentation保持沉默。
还有一个问题:从日志记录模块共享的锁导致了fork的问题。https://bugs.python.org/issue6721
我不确定这个问题是如何产生的。听起来,当当前线程派生时,进程中的任何锁的状态都被复制到子进程中(这看起来像是设计错误,肯定会死锁)。如果是这样的话,使用多进程真的可以防止这种情况(因为我可以自由地创建我的多进程。线程之后的池。锁是由其他线程创建和输入的,并且在线程使用非分叉安全日志记录模块启动之后)--多处理模块文档也没有说明是否进行多进程处理。池应该在锁之前分配。
用多进程替换线程.Lock Everywhere是否可以避免此问题,并允许我们安全地组合线程和分支?
推荐答案
这听起来像是在当前线程派生时将进程中的任何锁的状态复制到子进程中(这看起来像是设计错误,肯定会死锁)。
这不是设计错误,而是fork()
早于单进程多线程。所有锁的状态都被复制到子进程中,因为它们只是内存中的对象;进程的整个地址空间被复制,就像在fork中一样。只有糟糕的替代方案:要么通过派生复制所有线程,要么在多线程应用程序中拒绝派生。
因此,在多线程程序中执行fork()
从来都不是安全的操作,除非随后是子进程中的execve()
或exit()
。
用多进程替换线程.Lock Everywhere是否可以避免此问题,并允许我们安全地组合线程和分支?
否。组合线程和派生是不安全的,这是不可能的。
问题是,当一个进程中有多个线程时,在fork()
系统调用之后,您无法继续在POSIX系统中安全地运行该程序。
例如,Linux手册fork(2)
:
- 在多线程程序中执行
fork(2)
后,孩子可以安全地调用 只有异步信号安全功能(见signal(7)
),直到它 调用execve(2)
。
即,在多线程程序中fork()
,然后只调用Async-Signal-Safe函数(这是C函数的一个相当有限的子集),直到子进程被另一个可执行程序替换!
子进程中不安全的C函数调用例如
malloc
用于动态内存分配- 用于格式化输入的任何
<stdio.h>
函数 - 线程状态处理所需的大多数
pthread_*
函数,包括创建新线程...
因此,子进程实际上可以安全地做的事情很少。不幸的是,CPython核心开发人员一直在淡化由此导致的问题。即使现在documentation也是这样说的:
请注意,安全地派生多线程进程是 有问题。
这是"不可能"的委婉说法。
如果不是fork
使用fork
Start方法,则可以安全地从具有多个控制线程的的中使用多进程;在Python3.4+中,它是now possible to change the start method。在包括所有Python2在内的早期版本中,POSIX系统的行为总是像fork
被指定为启动方法一样;这将导致未定义的行为。
问题不仅限于<2-12]>对象,而且由C标准库、C扩展等持有的所有锁。更糟糕的是,大多数时候人们会说"它对我有效"...直到它停止工作。
甚至有这样的情况,在MacOS X中,看似单线程的Python程序实际上是多线程的,从而导致在使用多处理时出现故障和死锁。
另一个问题是,所有打开的文件句柄、它们的使用、共享套接字在派生的程序中的行为可能会很奇怪,但即使在单线程程序中也是如此。
TL;DR:在多线程程序中使用multiprocessing
,带C扩展,打开套接字等:
- 在3.4+&;POSIX中,如果显式指定的启动方法不是
fork
, - 在Windows中可以,因为它不支持派生;
- 在POSIX上的Python2-3.3中:你几乎是搬起石头砸自己的脚。
这篇关于在Python中从线程调用多处理安全吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!