我正在使用pool.map填充字典-称为节点。需要明确的是:该字典是在pool.map运行之后填充的,因此不必在进程之间共享变量。该函数返回的所有内容以及字典中的所有内容都是可提取的。它正在填充实质上是图的字典。当我深入1、2、3来填充此图时,程序将完美运行。但是在4点深处:该程序似乎没有崩溃,只是死机了。我在要映射到的函数中设置了打印语句,并在其运行结束时将其打印在程序的顶部,然后冻结。这是我如何调用pool.map:
currentNode = startingNode
nodesPopulated = [currentNode]
connections = []
merger = []
pool = Pool(cpu_count())
for currentDepth in range(1, depth):
print('=' * 70)
print("= At depth", currentDepth)
connections = []
for item in nodesPopulated:
if item != None:
if item.isPopulated():
connections +=list(item.getConnections().values())
print("= Current number of connections:",len(connections))
print("= Current number of NodesPopulated in this iteration: ",len(nodesPopulated))
print("= Total number of nodes",len(self.nodes.keys()))
nodesPopulated = pool.map(self.populateTopicNode, connections)
print('\n= Successfully populated another round of nodes')
for node in nodesPopulated:
if node != None:
if item.isPopulated():
self.nodes[node.getTopic().getName()] = node
#self.populatedNodes[node.getTopic().getName()] = True;
print('= Updated self.nodes\n')
pool.close()
pool.join()
print('\nCount = ',len(list(self.nodes.keys())))
return
再次,我确保返回到nodePopulated的所有内容都是可腌制的。我不知所措,因为要深入运行此程序4大约需要2个小时,而没有pool.map的话,它可以正常工作,但大约需要6个小时。我不想抛弃多处理程序,但是我无法弄清楚这一点,并且它需要永远进行调试。在冻结之前,它显示的最后一个内容是'D',它位于self.populateTopicNode的顶部。我还认为对象太大(self.nodes和连接数太大)可能是为什么冻结的原因。
注意: 我确定这是一个多处理问题,因为我没有使用pool.map即可运行此确切的代码,而是将其替换为for循环,并且运行到完成而没有错误。因此,导致pool.map冻结。在函数的第一个引用处没有错误消息挂起。这是“ populateTopicNode”的前几行:
def populateTopicNode(self, node: TopicNode):
print('D')
if(node.isPopulated()):
return None
冻结前控制台上最后看到的是'D'
,它使用大约1300 mb的内存挂起。
EDIT2:
好的,所以我发现它返回的不仅仅是随机挂起的东西。它返回None,然后挂起。我不确定为什么会这样,因为很多时候它返回None都可以正常工作。我还尝试包装了我的函数,只是要查看是否将异常返回给父对象是否很奇怪,这也不是问题。没有异常被捕获,它正在运行直至返回。它只是在返回后挂起。
EDIT3:
每一次迭代都会在相同的精确位置中断。我打印正在处理的当前Topic的名称,它总是在同一行的同一位置中断,然后挂起。我不确定是否有帮助,但这只是其他信息。始终在同一确切时间中断。
答案 0 :(得分:3)
根据multiprocessing准则。
应尽可能避免在进程之间转移大量数据。
multiprocessing.Pool
依赖于锁定的缓冲区(OS管道)在工作程序之间分配任务并检索其结果。如果将大于缓冲区的对象推入管道,则逻辑可能会挂起。
我建议您将作业转储到文件中(例如,使用pickle
),然后将文件名发送给子进程。这样,每个进程都可以独立检索数据。不仅可以防止逻辑卡住,而且可以发现速度提高以及管道成为设计中的严重瓶颈。
答案 1 :(得分:3)
我最近遇到一个案例,我需要提高多重处理的速度。我设法通过使用基于迭代器的方法并使用imap而不是map来提高速度。 Imap使用一个迭代器,并在迭代器上进行迭代。 Map实际上首先将第二个参数转换为列表,然后将其传递给它的工作人员。这可能是您的代码的瓶颈,但是我不是百分百确定的。
使用imap和迭代器至少可以节省内存,也许可以提高速度,还可以解决崩溃问题。
我提议像这样的事情。
from itertools import chain
connections = chain(map(
lambda item: item.getConnections.values(),
filter(lambda item : item != None and item.isPopulated(), nodesPopulated)
))
p.imap(self.populateTopicNode, connections, chunksize=1024)
注意:您应该测试我是否真的正确地将连接正确转换为迭代器。
注2:p.imap实际上返回一个迭代器。因此,函数本身是非阻塞的,但是当您在for循环中迭代其值时,它将阻塞(等待输出)。这可能取决于您的功能可能产生的副作用,也可能不是。否则在循环之前将其转换为列表。
注3:块大小是发送给工作程序的每个块的大小。如果该值太低,则将过多的新块请求发送到主进程,从而造成瓶颈。太高了,您可以一次强制太多内容。或者您最后创建闲置的工作程序,因为一个或两个工作程序仍在忙于处理其工作块。如果您正在使用的功能在所有任务中的结束时间大致相等,则最佳块大小将是任务数量除以工作程序(CPU)数量。这样,每个工作人员仅请求一次块。 Chunksize还是p.map中的一个参数,因此也许现在已经适用于您的代码。
注4:也有imap_unorded,它的功能相同,但是只要有准备就绪,返回的迭代器就会返回每个输出,因此它可能是“无序的”。也许使用它可能会很有趣,但是在我的情况下,整个迭代花费了比imap多的时间。
注5:就像我说的那样,我不知道您的程序为什么挂起,这种方法有助于加快我的程序的速度,但是您的程序可能会挂起,这取决于其他原因。 Noxdafox方法可能是一个更好的解决方案,因为您确实排除了主要过程作为瓶颈,但是,我认为,如果这种方法行之有效,那将是一个更为优雅的解决方案。