ThreadPoolExecutor、ProcessPoolExecutor 和全局变量

新手上路,请多包涵

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

The following code will output Hello as I expect, but when you change ThreadPoolExecutor for ProcessPoolExecutor , it will output None .

 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()

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

我知道我可以改变周围的事情,这会奏效,但我不明白为什么。例如,以下适用于两个执行器,但这也意味着必须对每个实例进行全局初始化。

 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor

greeting = None

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

    return None

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

    return None

def process():
    init()
    print(greeting)

    return None

if __name__ == '__main__':
    main()

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

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

原文由 Bram Vanroy 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.6k
2 个回答

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

 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 将是一个冻结的副本,因此对它的更改在进程中不可见,一旦您返回到 __main__ 块,它们也将不可见。 ThreadPoolExecutor 另一方面,将在线程之间共享 dict 对象。

原文由 jedwards 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

在 Unix 上,子进程可以使用在使用全局资源的父进程中创建的共享资源。

如多处理 文档 中所述。然而,出于某种神秘的原因,它无法在我运行 Mojave 的 Mac 上运行(它应该是一个 UNIX 兼容的操作系统;仅使用 Python 3.8 进行测试)。可以肯定的是,它不能在 Windows 上运行,而且通常不推荐使用多个进程。

原文由 FSch 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题