ThreadPoolExecutor,ProcessPoolExecutor和全局变量

时间:2018-06-15 08:55:56

标签: python python-3.x python-multiprocessing python-multithreading concurrent.futures

我是一般的并行化和特别是concurrent.futures的新手。我想对我的脚本进行基准测试并比较使用线程和进程之间的差异,但我发现我甚至无法运行,因为在使用ProcessPoolExecutor时我无法使用我的全局变量。

以下代码会按照我的预期输出Hello,但当您更改ThreadPoolExecutor的{​​{1}}时,会输出ProcessPoolExecutor

None

我不明白为什么会这样。在我的真实程序中,init用于将全局变量设置为CLI参数,并且有很多。因此,似乎不建议将它们作为参数传递。那么如何正确地将这些全局变量传递给每个进程/线程呢?

我知道我可以改变一切,这会有效,但我不明白为什么。例如。以下适用于两个Executors,但它也意味着必须为每个实例进行全局初始化。

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor

greeting = None

def process():
    print(greeting)

    return None


def main():
    with ThreadPoolExecutor(max_workers=1) as executor:
        executor.submit(process)

    return None


def init():
    global greeting
    greeting = 'Hello'

    return None

if __name__ == '__main__':
    init()
    main()

所以我的主要问题是,实际发生的事情。为什么这段代码适用于线程,而不适用于进程?而且,如何正确地将set globals传递给每个进程/线程,而不必为每个实例重新初始化它们?

(旁注:因为我已经读过在Windows上,concurrent.futures可能表现不同,我必须注意到我在Windows 10 64位上运行Python 3.6。)

4 个答案:

答案 0 :(得分:1)

实际上,OP的第一个代码将在Linux上按预期工作(在Python 3.6-3.8中进行了测试),因为

在Unix上,子进程可以利用在操作系统中创建的共享资源。 使用全局资源的父流程。

,如多处理doc中所述。但是,由于一个神秘的原因,它无法在运行Mojave的Mac上运行(应该是UNIX兼容的OS;仅在Python 3.8上进行了测试)。并且可以肯定的是,它在Windows上不起作用,通常不建议将其用于多个进程。

答案 1 :(得分:0)

我不确定这种方法的局限性,但您可以在主进程/线程之间传递(可序列化的?)对象。这也可以帮助你摆脱对全球变量的依赖:

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor

def process(opts):
    opts["process"] = "got here"
    print("In process():", opts)

    return None


def main(opts):
    opts["main"] = "got here"
    executor = [ProcessPoolExecutor, ThreadPoolExecutor][1]
    with executor(max_workers=1) as executor:
        executor.submit(process, opts)

    return None


def init(opts):                         # Gather CLI opts and populate dict
    opts["init"] = "got here"

    return None


if __name__ == '__main__':
    cli_opts = {"__main__": "got here"} # Initialize dict
    init(cli_opts)                      # Populate dict
    main(cli_opts)                      # Use dict

适用于两种执行者类型。

编辑:即使听起来它对您的用例来说不是一个问题,但我会指出ProcessPoolExecutoropts dict你进入process dict {1}}将是一个冻结的副本,因此在进程中不会看到突变,也不会在返回__main__块后看到它们。另一方面,ThreadPoolExecutor将在线程之间共享dict对象。

答案 2 :(得分:0)

让图像一个进程是一个框,而一个线程是一个框内的工作者。工作人员只能访问框中的资源,不能触及其他框中的其他资源。

因此,当您使用线程时,您将为当前框(主进程)创建多个工作线程。但是当你使用进程时,你正在创建另一个框。在这种情况下,此框中初始化的全局变量与另一个框中的全局变量完全不同。这就是为什么它没有像你期望的那样工作的原因。

jedwards提供的解决方案在大多数情况下都足够好。您可以将资源打包在当前框中(序列化变量)并将其传递到另一个框(传输到另一个进程),以便该框中的工作人员可以访问资源。

答案 3 :(得分:0)

进程表示在主要进程中运行线程时,在术语的OS含义中在单独进程中运行的活动。每个进程都有自己独特的命名空间。

您的主进程通过在greeting条件中为其自己的命名空间调用init()将值设置为__name__ == '__main__'。在您的新流程中,这不会发生(此处__name__'__mp_name__')因此greeting仍为无,并且init()永远不会被实际调用,除非您在函数中明确地执行此操作过程执行。

虽然通常不建议在进程之间共享状态,但有一些方法可以这样做,如@jedwards回答中所述。

您可能还想查看文档中的Sharing State Between Processes