【问题标题】:Is python multiprocessing with inter-process signalling via a global flag variable safe?通过全局标志变量进行进程间信令的python多处理安全吗?
【发布时间】:2018-05-31 11:29:52
【问题描述】:

我正在运行许多子进程(比我拥有的内核还多),如果其中一个满足特定条件,我会设置一个全局变量的值 global bailout

如果设置了bailout,那么所有后续子进程都会尽快退出。

参见例如这个简单的例子,我将我的 20 次调用的结果乘以 loop() 函数,但如果其中任何一个调用返回零,我就会“退出”:

import sys
import random
import multiprocessing

def loop(tup):
    global bailout
    if bailout==1:                    # obey a global bail out "flag"
        return 0
    x = random.random() - 0.5
    if x < 0:
        bailout = 1                   # set a global bail out "flag"
        return 0
    return x

def top():
    global bailout
    bailout = 0
    runtups = 20 * [[0]]              # a dummy parameter [0] for function "loop"
    pool = multiprocessing.Pool()
    results = pool.imap(loop, runtups)
    pool.close()
    res = 1
    sys.stdout.write("1")
    for result in results:
        sys.stdout.write(" * %g" % result)
        res = res * result
    sys.stdout.write(" = %g\n" % res)

top()

它工作得很好(或者准确地说,每次我尝试过它都有效)。即我的桌面有 4 个内核,如果前 4 个子进程之一将 bailout 设置为 1(在本示例中几乎总是发生),那么所有后续运行都会在 if bailout==1 条件下退出。 p>

但它安全吗?

我的意思是,子进程所能做的就是将 bailout 设置为 1。但是如果两个子进程都想将 bailout 设置为 1 怎么办?他们是否有可能同时尝试,导致救助变得不确定?还是保证这永远不会发生,(可能是因为顶层进程总是串行处理已完成的子进程?)

【问题讨论】:

  • 如果不是很明显,我要做的就是从并行循环中“中断”,一旦我知道运行“循环”的剩余运行不会影响我的答案。所以真正的问题是,打破并行循环的最佳方法是什么。

标签: python parallel-processing multiprocessing global globals


【解决方案1】:

全局变量不在进程之间共享。如果你添加一些日志到loop,你可以看到真正发生了什么:

def loop(tup):
    global bailout
    if bailout==1:
        print(f'pid {os.getpid()} had bailout 1')
        return 0
    x = random.random() - 0.5
    if x < 0:
        print(f'pid {os.getpid()} setting bailout 1')
        bailout = 1
        return 0
    return x

这将产生如下输出:

pid 30011 setting bailout 1
pid 30013 setting bailout 1
pid 30015 setting bailout 1
pid 30009 setting bailout 1
pid 30010 setting bailout 1
pid 30011 had bailout 1
pid 30013 had bailout 1
pid 30009 had bailout 1
pid 30014 setting bailout 1
pid 30015 had bailout 1
pid 30010 had bailout 1
pid 30011 had bailout 1
1 * 0.494123 * 0.0704172 * 0 * 0.10829 * 0 * 0.465238 * 0 * 0.0638724 * 0 * 0 * 0 * 0.227231 * 0 * 0 * 0 * 0 * 0 * 0 * 0.463628 * 0.372984 = 0

发生的情况是multiprocessing.Pool() 正在启动 4 个进程,这些进程在可用时会被重新使用。因此,在处理 runtups 中的 20 个项目时,最终每个单独的进程都将其 bailout 设置为 1。当重用该进程时,它会触发 bailout 子句。

由于您随机决定何时设置bailout = 1,因此在处理这 20 个项目时它可能永远不会发生,或者它可能发生在某些进程中而不是其他进程中,因此您可能不会得到我粘贴的相同结果以上,但至少有一些进程可能会进入救助模式。

如果您正在寻找一种在进程之间共享状态的可靠方法,请查看https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes

【讨论】:

    【解决方案2】:

    有可能吗?
    安全吗?
    有保证吗?

    虽然 GIL 步进确实使所有基于线程(而不是基于子进程)的多处理工作仍然出现在纯[SERIAL] 处理流中,但问题更多的是关于一种主要方法以及上述所有方法是否提出的问题得到安全解决。


    而不要试图违背记录在案的建议:

    最好让我们提及文档中的明确声明:

    16.6.3. Programming guidelines

    ...

    将资源显式传递给子进程

    在 Unix 上,子进程可以使用在父进程中使用全局资源创建的共享资源。但是,最好将对象作为参数传递给子进程的构造函数。

    除了使代码(可能)与 Windows 兼容之外,这还确保只要子进程还活着,该对象就不会在父进程中被垃圾收集。如果在父进程中对对象进行垃圾回收时释放了某些资源,这可能很重要。


    16.6.3.2 Windows

    ...

    全局变量

    请记住,如果在子进程中运行的代码尝试访问全局变量,那么它看到的值(如果有)可能与 @ 时父进程中的值不同987654325@ 被调用。

    但是,只是模块级常量的全局变量不会引起任何问题。


    除了帮助交流或“共享”状态的本机 pythonic 工具(不仅我提倡,在可能的情况下,最好不要共享),还有一些智能工具用于设计 ,使用多代理概念,其中每个线程都可以使用其他轻量级通信工具,与本机 GIL 步进操作所允许的相比,性能受到的惩罚更少(参考 ZeroMQ、nanomsg 等)。

    【讨论】:

      猜你喜欢
      • 2019-12-12
      • 2023-03-08
      • 2016-01-18
      • 2023-03-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多