可以安全地从Python中的线程调用多处理?

时间:2017-09-27 04:51:42

标签: python python-multiprocessing

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

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

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

我不确定这个问题是如何产生的。听起来当进程中的任何锁的状态都被复制到当前线程分叉的子进程中(这看起来像设计错误并且肯定会死锁)。如果是这样,使用多处理确实提供了任何保护(因为我可以在threading之后创建我的multiprocessing.Pool.Lock是由其他线程创建并输入的,并且在线程启动之后使用not-fork-safe日志记录模块) - 多处理模块docs也没有说明是否应该在Locks之前分配multiprocessing.Pools。

用多处理替换threading.Lock.Lock到处都避免这个问题并允许我们安全地组合线程和分支吗?

1 个答案:

答案 0 :(得分:5)

  

听起来当进程中的任何锁的状态都被复制到当前线程分叉的子进程中(这看起来像设计错误并且肯定会死锁)。

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

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

  

用多处理替换threading.Lock.Lock到处都避免这个问题并允许我们安全地组合线程和分支吗?

没有。没有什么比将线程和分叉组合起来更安全,无法完成。

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

例如,Linux手册fork(2)

  
      
  • 在多线程程序中fork(2)之后,孩子可以安全地打电话         只有异步信号安全功能(见signal(7))直到它为止         致电execve(2)
  •   

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

例如,

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

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

因此,儿童过程实际上可以安全地做什么。不幸的是,CPython核心开发人员一直在淡化由此引起的问题。即使现在documentation说:

  

请注意,安全分叉多线程进程是   的问题

对于&#34;不可能&#34;相当委婉。

使用多处理来自的Python进程是安全的,该进程具有多个控制线程,前提是您使用fork start方法 ;在Python 3.4+中它是now possible to change the start method。在以前的Python版本中,包括所有Python 2,POSIX系统总是表现为将fork指定为start方法;这会导致未定义的行为。

问题不仅限于threading.Lock个对象,还包括C标准库,C扩展等所持有的所有锁。更糟糕的是,大多数人会说& #34;它适用于&#34; ...直到它停止工作。

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

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

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

  • 罚款3.4+&amp;如果您明确指定不是fork
  • 的起始方法,则POSIX
  • 在Windows中很好,因为它不支持分叉;
  • 在Python 2中的
  • - 在POSIX上的3​​.3:你大部分时间都会自己开枪。