python - Python 并发期货 ProcessPoolExecutor 和全局变量:在 Linux 上工作,在 MacOS 上出错
问题描述
下面的代码示例按照我认为应该在两台 Linux 机器上运行:在运行 Red Hat 4.8.5-39 内核的大型基于 CentOS 的服务器上使用 Python 3.6.8,并在我的基于 MX 的机器上运行 Python 3.7.3 Debian 8.3.0-6 内核)。
$ python3 testshared.py filename.dat
filename.dat
270623586670000.0
但是,在我的 Mac 上运行 Mojave 10.14.6,使用 Python 3.8.3,我收到一个错误,因为foo=[]
在 function 中processBigFatRow()
。注意是在启动进程池之前foo
分配的。就像在 Linux 中一样,assign in的版本被传递给进程,而在 Mac 上,进程只使用代码顶部的初始化(我必须把它放在那里,所以它们是可变的)。getBigFatData()
foo
getBigFatData()
global
我了解该进程是主进程的“独立副本”,您不能在一个进程中分配全局变量并期望它们在另一个进程中发生变化。但是,在并行进程启动之前已经设置的变量,并且仅作为参考使用呢?就像跨操作系统的进程副本不一样。哪个“按设计”工作?
代码示例:
import pylab as pl
from concurrent import futures
import sys
foo = []
bar = []
def getBigFatData(filename):
global foo, bar
# get the big fat data
print(filename)
foo = pl.arange(1000000).reshape(1000,1000)
# compute something as a result
bar = pl.sum(foo, axis=1)
def processBigFatRow(row):
total = pl.sum(foo[row,:]**2) if row % 5 else bar[row]
return total
def main():
getBigFatData(sys.argv[1])
grandTotal = 0.
rows = pl.arange(100)
with futures.ProcessPoolExecutor() as pool:
for tot in pool.map(processBigFatRow, rows):
grandTotal+=tot
print(grandTotal)
if __name__ == '__main__':
main()
编辑:
正如建议的那样,我在我的 MX-Linux 机器上测试了 Python 3.8.6,它可以工作。
所以它适用于使用 Python 3.6.8、3.7.3 和 3.8.6 的 Linux。但它不在使用 Python 3.8.3 的 Mac 上。
编辑2:
来自多处理文档:
在 Unix 上,子进程可以使用在父进程中使用全局资源创建的共享资源。
所以它不能在 Windows 上运行(这不是最佳实践),但它不应该在 Mac 上运行吗?
解决方案
这是因为,在 MacOS 上,默认的多处理启动方法在 Python 3.8中发生了变化。它从fork
(py37) 变为spawn
(py38),导致其咬牙切齿。
在 3.8 版更改: 在 macOS 上,
spawn
启动方法现在是默认方法。start 方法应该被认为是不安全的fork
,因为它可能导致子进程崩溃。请参阅 bpo-33725。
With spawn
: 全局变量不与多进程进程共享。
因此,实际上,作为一种快速修复,'fork'
在您的所有调用中指定一个上下文ProcessPoolExecutor
,使用mp.get_context('fork')
. 但请注意上述警告;一个长期的解决方案是使用多处理文档中列出的一种技术来共享变量。
例如,在上面的代码中,替换:
with ProcessPoolExecutor() as pool:
...
和:
import multiprocessing as mp
with ProcessPoolExecutor(mp_context=mp.get_context('fork')) as executor:
...
替代方案:
当您只是编写一两个小脚本,并确定没有人在某处使用不同的地方调用您的代码时,您可以在代码块main
中一劳永逸地设置默认启动方法:main
mp.set_start_method
if __name__ == '__main__':
mp.set_start_method('fork')
...
但总的来说,我更喜欢第一种方法,因为您不必假设调用者事先设置了 start 方法。而且,根据文档:
请注意,这应该最多调用一次,并且应该
if __name__ == '__main__'
在主模块的子句中受到保护。
推荐阅读
- java - OKHttp的BufferedSink(或BufferedSource)中如何实现seek()函数?
- python - Tkinter 通过命令 lambda 传递 StringVar.get 给出初始值
- c++ - 为什么我的多线程代码比串行版本慢,我该怎么做才能加快速度?
- vue.js - 如何将索引附加到 v 模型?
- c++ - Windows.h Sleep(x) 中是否实现了流刷新?
- c - 在 Ubuntu 中使用 SDL(简单 DirectMedia 层)库
- javascript - 如何使用 JS 创建邮政编码检查器
- html - 如何在 HTML/CSS 中居中文本
- node.js - 运行笑话测试时无法附加调试器
- css - 尝试为文本转换和文本装饰设置动画。适用于除 Safari 之外的任何地方(bot 移动和桌面)