Python并发进程PoolExecutor和全局变量:在Linux上工作,在MacOS上出错

2024-06-02 12:08:55 发布

您现在位置:Python中文网/ 问答频道 /正文

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

$ python3 testshared.py filename.dat
filename.dat
270623586670000.0

但是,在运行Mojave10.14.6的Mac上,使用Python 3.8.3,我得到一个错误,因为函数processBigFatRow()中有foo=[]。请注意,foo是在启动进程池之前在getBigFatData()中分配的。就像在Linux中一样,在getBigFatData()中分配的foo的版本被传递给进程,而在Mac上,进程只使用代码顶部的初始化(我必须将其放在那里,以便它们是global变量)

我知道流程是主流程的“独立副本”,您不能在一个流程中分配全局变量,而期望它们在另一个流程中发生变化。但是,对于在并行进程启动之前已经设置好的、仅用于引用的变量,该怎么办呢?就好像整个OSs的过程拷贝是不一样的。哪一个“按设计”工作

代码示例:

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,它可以正常工作

因此,它可以在Linux上使用Python 3.6.8、3.7.3和3.8.6。 但在Mac上使用Python3.8.3时,它不起作用

编辑2:

multiprocessing doc

On Unix a child process can make use of a shared resource created in a parent process using a global resource.

所以它在Windows上不起作用(这不是最佳实践),但它在Mac上不起作用吗


Tags: 代码import机器foo进程linuxmacbar
2条回答

这是因为,在MacOS上,Python3.8中的default multiprocessing start method has changed。它从fork(py37)到spawn(py38),导致相当一部分咬牙

Changed in version 3.8: On macOS, the spawn start method is now the default. The fork start method should be considered unsafe as it can lead to crashes of the subprocess. See bpo-33725.

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

因此,实际上,作为一个快速修复方法,使用^{}在所有^{}调用中指定一个'fork'上下文。但是要注意上面的警告;一个长期的解决办法是使用多处理docs上列出的技术之一共享变量

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

with ProcessPoolExecutor() as pool:
    ...

与:

import multiprocessing as mp

with ProcessPoolExecutor(mp_context=mp.get_context('fork')) as executor:
    ...

备选方案

当您正在编写一两个小脚本,并且确信没有人在某处使用不同的main调用您的代码时,您可以使用^{}main代码块中一次性设置默认的启动方法:

if __name__ == '__main__':
    mp.set_start_method('fork')
    ...

但一般来说,我更喜欢第一种方法,因为您不必假设调用方事先设置了start方法。根据文件:

Note that this should be called at most once, and it should be protected inside the if __name__ == '__main__' clause of the main module.

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

相关问题 更多 >