我有一个非常大(只读)的数据数组,我希望由多个进程并行处理。
我喜欢Pool.map函数,并希望用它来并行计算该数据的函数。
我看到可以使用Value或Array类在进程之间使用共享内存数据。但是当我尝试使用它时,我得到一个RuntimeError:'SynchronizedString对象只应在使用Pool.map函数时通过继承在进程之间共享:
以下是我尝试做的简化示例:
from sys import stdin
from multiprocessing import Pool, Array
def count_it( arr, key ):
count = 0
for c in arr:
if c == key:
count += 1
return count
if __name__ == '__main__':
testData = "abcabcs bsdfsdf gdfg dffdgdfg sdfsdfsd sdfdsfsdf"
# want to share it using shared memory
toShare = Array('c', testData)
# this works
print count_it( toShare, "a" )
pool = Pool()
# RuntimeError here
print pool.map( count_it, [(toShare,key) for key in ["a", "b", "s", "d"]] )
有谁能告诉我这里我做错了什么?
所以我想做的是在进程池中创建新进程后,将有关新创建的共享内存分配数组的信息传递给进程。
答案 0 :(得分:39)
再次尝试,因为我刚看到赏金;)
基本上我认为错误信息意味着它所说的 - 多处理共享内存数组不能作为参数传递(通过pickling)。序列化数据没有意义 - 重点是数据是共享内存。所以你必须使共享数组全局化。我认为把它作为模块的属性更简洁,就像在我的第一个答案中一样,但是在你的例子中将它作为全局变量保留也很有效。考虑到你不希望在fork之前设置数据,这是一个修改过的例子。如果你想拥有多个可能的共享数组(这就是为什么你想将toShare作为参数传递),你可以类似地创建共享数组的全局列表,并将索引传递给count_it(这将成为{{1} })。
for c in toShare[i]:
[编辑:上面因为没有使用fork而无法在Windows上运行。但是,下面的内容适用于Windows,仍然使用Pool,所以我认为这是最接近你想要的:
from sys import stdin
from multiprocessing import Pool, Array, Process
def count_it( key ):
count = 0
for c in toShare:
if c == key:
count += 1
return count
if __name__ == '__main__':
# allocate shared array - want lock=False in this case since we
# aren't writing to it and want to allow multiple processes to access
# at the same time - I think with lock=True there would be little or
# no speedup
maxLength = 50
toShare = Array('c', maxLength, lock=False)
# fork
pool = Pool()
# can set data after fork
testData = "abcabcs bsdfsdf gdfg dffdgdfg sdfsdfsd sdfdsfsdf"
if len(testData) > maxLength:
raise ValueError, "Shared array too small to hold data"
toShare[:len(testData)] = testData
print pool.map( count_it, ["a", "b", "s", "d"] )
不确定为什么map不会Pickle数组但是Process和Pool会 - 我想也许它已经在windows上的子进程初始化时传输了。请注意,数据仍然在fork之后设置。
答案 1 :(得分:5)
我看到的问题是Pool不支持通过其参数列表来搜索共享数据。这就是错误消息所指的“对象应该只通过继承在进程之间共享”。如果要使用Pool类共享共享数据,则需要继承共享数据。
如果需要显式传递它们,则可能必须使用multiprocessing.Process。这是你重写的例子:
from multiprocessing import Process, Array, Queue
def count_it( q, arr, key ):
count = 0
for c in arr:
if c == key:
count += 1
q.put((key, count))
if __name__ == '__main__':
testData = "abcabcs bsdfsdf gdfg dffdgdfg sdfsdfsd sdfdsfsdf"
# want to share it using shared memory
toShare = Array('c', testData)
q = Queue()
keys = ['a', 'b', 's', 'd']
workers = [Process(target=count_it, args = (q, toShare, key))
for key in keys]
for p in workers:
p.start()
for p in workers:
p.join()
while not q.empty():
print q.get(),
输出:('s',9)('a',2)('b',3) ('d',12)
队列元素的排序可能会有所不同。
为了使它更通用且类似于Pool,您可以创建固定的N个进程,将键列表拆分为N个部分,然后使用包装器函数作为Process目标,它将为每个键调用count_it在列表中传递,如:
def wrapper( q, arr, keys ):
for k in keys:
count_it(q, arr, k)
答案 2 :(得分:2)
如果只读取数据,只需将它作为模块中的变量来自Pool的分支。然后所有子进程都应该能够访问它,并且如果你没有写入它就不会被复制。
import myglobals # anything (empty .py file)
myglobals.data = []
def count_it( key ):
count = 0
for c in myglobals.data:
if c == key:
count += 1
return count
if __name__ == '__main__':
myglobals.data = "abcabcs bsdfsdf gdfg dffdgdfg sdfsdfsd sdfdsfsdf"
pool = Pool()
print pool.map( count_it, ["a", "b", "s", "d"] )
如果您确实想尝试使用Array,可以尝试使用lock=False
关键字参数(默认情况下为true)。
答案 3 :(得分:0)
如果遇到RuntimeError: Synchronized objects should only be shared between processes through inheritance
错误,请考虑使用multiprocessing.Manager
,因为它没有此限制。经理考虑到它可能完全在一个单独的过程中运行。
import ctypes
import multiprocessing
manager = multiprocessing.Manager()
counter = manager.Value(ctypes.c_ulonglong, 0)
counter_lock = manager.Lock() # pylint: disable=no-member
with counter_lock:
counter.value = count = counter.value + 1
答案 4 :(得分:-1)
所以你对sharedctypes
的使用是错误的。您是否希望从父进程继承此数组,或者您希望显式传递它?在前一种情况下,您必须创建一个全局变量,如其他答案所示。但您无需使用sharedctypes
明确传递,只需传递原始testData
。
顺便说一句,你对Pool.map()
的使用是错误的。它与内置map()
函数具有相同的界面(您是否将其与starmap()
混淆了?)。下面是一个工作示例,显式传递数组:
from multiprocessing import Pool
def count_it( (arr, key) ):
count = 0
for c in arr:
if c == key:
count += 1
return count
if __name__ == '__main__':
testData = "abcabcs bsdfsdf gdfg dffdgdfg sdfsdfsd sdfdsfsdf"
pool = Pool()
print pool.map(count_it, [(testData, key) for key in ["a", "b", "s", "d"]])