map()和pool.map()的区别

3
我有这样的一段代码。
def plotFrame(n):
    a = data[n, :]
    do_something_with(a)

data = loadtxt(filename)
ids = data[:,0]  # some numbers from the first column of data
map(plotFrame, ids)

这对我来说很有效。现在我想尝试用pool.map()替换map(),如下所示:

pools = multiprocessing.Pool(processes=1)
pools.map(plotFrame, ids)

但这样不行,这句话的意思是:
NameError: global name 'data' is not defined

问题是:发生了什么?为什么 map() 不会抱怨未传递到函数的data变量,而 pool.map() 会抱怨?
编辑: 我使用Linux。
编辑2: 根据@Bill的第二个建议,我现在有了以下代码:
def plotFrame_v2(line):
    plot_with(line)

if __name__ == "__main__":
    ff = np.loadtxt(filename)
    m = int( max(ff[:,-1]) ) # max id
    l = ff.shape[0]
    nfig = 0
    pool = Pool(processes=1)
    for i in range(0, l/m, 50):
        data = ff[i*m:(i+1)*m, :] # data of one frame contains several ids
        pool.map(plotFrame_v2, data)
        nfig += 1        
        plt.savefig("figs_bot/%.3d.png"%nfig) 
        plt.clf() 

这正如预期的那样工作。然而,现在我又遇到了一个意想不到的问题:生成的图表是空白的,而使用 map() 的上面的代码会产生带有 data 内容的图表。


2
你是在Windows上运行吗?使用multiprocessing时最好不要依赖全局变量;特别是在Windows上,生成的子进程可能无法看到全局变量的更改。 - Martijn Pieters
这个问题可能会有所帮助,虽然我不太了解,但它可以帮助:https://dev59.com/dG035IYBdhLWcg3wZvJg - ederollora
2个回答

4
使用,您可以生成独立进程以处理共享资源data。通常,您可以通过将该资源明确声明为global,允许进程在父进程中使用共享资源。然而,更好的做法是将所有需要的资源作为函数参数明确传递给子进程。如果您正在Windows上工作,则必须这样做。请查看此处的多处理指南

所以您可以尝试执行

data = loadtxt(filename)

def plotFrame(n):
    global data
    a = data[n, :]
    do_something_with(a)

ids = data[:,0]  # some numbers from the first column of data
pools = multiprocessing.Pool(processes=1)
pools.map(plotFrame, ids)

或者更好的方法是参考这个线程,了解如何使用multiprocessing.pool将多个参数传递给函数。一个简单的方法可以是:

def plotFrameWrapper(args):
    return plotFrame(*args)

def plotFrame(n, data):
    a = data[n, :]
    do_something_with(a)

if __name__ == "__main__":
    from multiprocessing import Pool
    data = loadtxt(filename)
    pools = Pool(1)

    ids = data[:,0]
    pools.map(plotFrameWrapper, zip([data]*len(inds), inds))
    print results

最后一件事情:根据您的示例看起来,您所做的唯一操作就是对数组进行切片,因此您可以先对数组进行切片,然后将切片后的数组传递给函数:

def plotFrame(sliced_data):
    do_something_with(sliced_data)

if __name__ == "__main__":
    from multiprocessing import Pool
    data = loadtxt(filename)
    pools = Pool(1)

    ids = data[:,0]
    pools.map(plotFrame, data[ids])
    print results

pools.map(zip([data]*len(inds), inds))的意思是:你的意思是pools.map(plotFrameWrapper, zip([data]*len(inds), inds)),我猜测是这样。这个解决方案很好用。谢谢。然而,我不理解你的第二个建议。原则上,我现在正在对数据进行逐帧切片。然后使用map()函数对切片数组进行逐行处理。 - Tengis
是的,在zip部分你发现得很好。至于第二个建议,NumPy数组在可迭代的意义上是可迭代的,如果你说for x in 2d_array,每个x代表一行。因此,如果你按帧切片data然后将其馈送到map中,map将遍历切片数组的行。 - wflynny

2
为避免“意外”问题,请避免使用全局变量。
要使用内置的map函数调用plotFrame来重现您的第一个代码示例:
def plotFrame(n):
    a = data[n, :]
    do_something_with(a)

使用 multiprocessing.Pool.map,首先要处理全局变量 data。如果 do_something_with(a) 也使用了一些全局数据,则也需要进行更改。
要了解如何将一个 numpy 数组传递给子进程,请参见 在多进程中使用共享内存的 numpy 数组。如果不需要修改数组,则更加简单:
import numpy as np

def init(data_): # inherit data
    global data #NOTE: no other globals in the program
    data = data_

def main():
    data = np.loadtxt(filename) 
    ids = data[:,0]  # some numbers from the first column of data
    pool = Pool(initializer=init, initargs=[data])
    pool.map(plotFrame, ids)

if __name__=="__main__":
    main()

所有的参数都应该通过作为 plotFrame 的参数显式传递或通过 init() 继承。
你的第二个代码示例再次尝试操作全局数据(通过 plt 调用):
import matplotlib.pyplot as plt

#XXX BROKEN, DO NOT USE
pool.map(plotFrame_v2, data)
nfig += 1        
plt.savefig("figs_bot/%.3d.png"%nfig) 
plt.clf()

除非您在主进程中绘制一些东西,否则此代码将保存空白图形。要么在子进程中绘制图形,要么显式地将要绘制的数据发送到父进程,例如通过从plotFrame返回它并使用pool.map()返回值。这里是一个代码示例:如何在子进程中绘图

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接