Python 并发 future ProcessPoolExecutor 和全局变量:在 Linux 上工作,在 MacOS 上出错

FSc*_*Sch 7 python linux macos global-variables concurrent.futures

下面的代码示例按照我的想法在两台 Linux 机器上运行:在运行 Red Hat 4.8.5-39 内核的基于 CentOS 的大型服务器上使用 Python 3.6.8,在运行 Red Hat 4.8.5-39 内核的基于 MX 的机器上使用 Python 3.7.3 Debian 8.3.0-6 内核)。

$ python3 testshared.py filename.dat
filename.dat
270623586670000.0
Run Code Online (Sandbox Code Playgroud)

但是,在运行 Mojave 10.14.6 的 Mac 上,使用 Python 3.8.3,我收到错误,因为foo=[]在 function 中processBigFatRow()。注意是在启动进程池之前foo分配的。就像在 Linux 中一样,分配的版本被传递给进程,而在 Mac 上,进程只使用代码顶部的初始化(我必须将其放在那里,以便它们是可变的)。getBigFatData() foogetBigFatData()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()

Run Code Online (Sandbox Code Playgroud)

编辑:

按照建议,我在我的 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 上工作吗?

Pie*_*e D 8

这是因为,在 MacOS 上,默认的多处理启动方法在 Python 3.8 中已更改。它从fork(py37)到spawn(py38),引起了相当大的咬牙切齿的份额。

版本 3.8 中的更改:在 macOS 上,spawn启动方法现在是默认方法。startfork方法应该被认为是不安全的,因为它可能导致子进程崩溃。请参阅 bpo-33725

With spawn:全局变量不与多进程进程共享。

因此,实际上,作为一个快速解决方案,可以'fork'在所有ProcessPoolExecutor, 调用中使用指定上下文mp.get_context('fork')。但请注意上面的警告;一个长期的解决方案是使用多处理文档中列出的技术之一来共享变量。

例如,在上面的代码中,替换:

with ProcessPoolExecutor() as pool:
    ...
Run Code Online (Sandbox Code Playgroud)

和:

import multiprocessing as mp

with ProcessPoolExecutor(mp_context=mp.get_context('fork')) as executor:
    ...
Run Code Online (Sandbox Code Playgroud)

选择

当您只是编写一两个小脚本,并且确定没有人使用不同的地方会调用您的代码时,您可以使用以下命令在代码块main中一劳永逸地设置默认启动方法:mainmp.set_start_method

if __name__ == '__main__':
    mp.set_start_method('fork')
    ...
Run Code Online (Sandbox Code Playgroud)

但一般来说,我更喜欢第一种方法,因为您不必假设调用者已经预先设置了启动方法。并且,根据文档:

请注意,最多应调用一次,并且应将其保护在if __name__ == '__main__'主模块的子句内。


小智 1

您正在比较两个不同 python 版本中相同代码的输出。内置模块可能是相同的,也可能在 3.6 和 3.8 之间发生了显着变化。在继续之前,您应该在两个地方的相同 python 版本上运行代码。