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

3 投票
2 回答
1693 浏览
提问于 2025-04-17 23:59

我有一段代码如下:

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)

这段代码对我来说运行得很好。现在我想试着把 map() 替换成 pool.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() 的代码生成的图形是有内容的。

2 个回答

4

使用 multiprocessing.pool,你可以创建多个独立的进程来处理共享的(全局的)资源 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
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() 的返回值。这里有一个代码示例:如何在子进程中绘图

撰写回答