我试图在Python中使用pythonocc-core创建一个复杂的CAD几何图形。为了加快计算速度,我使用内置的并行计算在一个复杂的固体切割孔。为了克服内存限制,我将切割过程分批进行,并且为了避免以前遇到的内存泄漏,我使用multiprocessing.Process()在单独的进程中进行切割操作。最简单的传递结果的方法是,首先将部分几何体写入步骤文件。但是,我想尝试直接返回剪切的结果,而不需要编写文件。
我的代码看起来像这样:
QQ=multiprocessing.Manager().Queue() # I have also tried with multiprocessing.Queue() but the result is the same
for block in list_blocks:
p1=multiprocessing.Process(target=cutting_function, args=(result, block, QQ, #others)
p1.start()
result=QQ.get()
p1.join()
函数是这样的:
def cutting_function(result, block, QQ, #others):
#various calculations
QQ.put(result)
程序执行了一定数量的(少量)迭代,然后突然似乎卡住了,不进行下一次迭代(与前一次迭代相比,我等待了大量的时间,但没有进步)
我在Anaconda环境中使用Python 3.11.5,在Windows 11上使用WSL 2 Ubuntu
我试过multiprocessing.Queue()和multiprocessing.Manager().Queue()我试过在for循环之外创建一次QQ,但也试过在每个循环中重新创建它我也试过在QQ.get()之前加入p1,但没有区别。
1条答案
按热度按时间fnvucqvd1#
下面是一个很长的答案。简短版本:我很确定您对
cutting_function
的一个调用在将任何内容放入队列之前就结束了。所以主进程永远等待队列中的某个东西。如果您知道如何纠正cutting_function
中的“结束而不将某些内容放入队列”错误,那么就去做吧,而无需阅读其余部分。否则:……所以,如果我理解正确的话,你并不真的需要多处理。您只想隔离子进程中的每个调用,以便由该进程创建的任何混乱(内存泄漏)都随之消失。
不用说,我想,找到内存泄漏的原因并解决它会更好。
那你现在做的就是
它运行起来没有任何问题,显示了100行,从
0 1000
到99 1099
。我想是的。而且没有任何类型的并行性(即使我有一个复杂的计算,而不是1000+arg,它也会是一个接一个的)。请注意,我跳过了经理。如果只有一个进程,就不需要管理器。
问题是,使用队列获取返回值的方式意味着每个进程只在其上放置一个值。
看另一个例子
这一次它工作得很好,很好…直到第50轮。在这一点上,它只是冻结。应该是的第50个进程运行并退出,没有将任何内容放入队列。然后你试图从队列中接收一些东西。什么都没有。所以它会阻塞,等待有东西出现。
注意,这个例子的一个变体可以是这个函数
我的观点是:为什么你可能会得到不值的原因,它是因为一个进程崩溃之前,它可以返回任何东西。如果没有在单独的进程中运行,该崩溃将导致整个代码崩溃。但在这里,它只是一个崩溃的过程。它在屏幕上显示错误消息,但主进程仍在继续。仍然在等待队列中出现的东西。
你的用法可能会让人惊讶。人们可能会想,在流程完成后,它怎么会期望队列中出现一些东西。但那是因为你对队列的使用并不是通常的那种。通常情况下,当你并行运行10个进程时,应该使用它,并且在主进程中,等待结果,无论顺序如何(不一定是你开始进程的顺序)。
对于您的“单处理”用法,使用非常顺序的:我启动一个进程,进程结束,我得到唯一的结果,
Value
可能更适合(使用默认值,以便您可以知道,当进程完成时,没有返回值)。另一种解决方案是简单地测试队列是否为空
第50行显示错误(在子进程中),然后在主进程中显示“无结果”。但之后它继续。
然而,
multiprocessing
文档指出,在QQ.put()
和该结果可用之间可能存在“无穷小时间”。所以,有一个轻微的风险(即使有数百万次尝试,我也看不到),队列在很短的时间内是空的。如果是QQ.get()
,那就不重要了。这意味着QQ.get()
等待的时间是无限小的。对于我的后一种解决方案,这种轻微的风险是我们错过一次结果的风险。然后每个QQ.get()
得到前一个结果(第100个结果丢失)。因此,另一种解决方案是确保每个子流程都推送一些内容
注意
QQ.put
后面的return
。这是为了确保我们不会把两件事放在队列中。在这样一个简单的代码,没有风险。但在更复杂的代码中,这意味着如果我们到达了末尾(函数的最后一行),这要么是因为我们崩溃了,要么是因为我们忘记在队列中放入一些东西。那些
error
/no result
是哨兵。确保队列中有东西,即使什么都没有。还请注意,当我说“崩溃”时,我指的是Python错误。一个引发异常的异常,可以被
try/except
捕获。如果它是一个真实的的崩溃(例如segfault)。只有当你在python解释器中发现一个bug时,或者如果你玩了一些火,比如C扩展代码,或者ctypes
),那么我的except
将什么也不会捕获,所以队列中什么也没有。尽管如此,主进程仍然会很好地活着,因为它们是不同的进程。此示例在第30行冻结。因为第30道工序segfault。因此,它不仅没有
QQ.put
log(49-30)
,而且也没有QQ.put
哨兵:整个Python进程崩溃了。但是,主进程仍然在那里,等待QQ.get()
在队列中找到一些东西。所以,如果有发生这种情况的风险,你可以混合吊带和腰带:
所以,如果进程正常工作,那么,如果队列有一个结果。
如果进程忘记返回队列中的某些内容,或者引发异常,那么队列中会有一个哨兵(“no result”或“error”),所以队列中仍然有一些内容。
如果进程完全崩溃(例如,由于segfault),那么
QQ.empty
会看到它并处理这种情况。最后,如果出现了“无限小的时间”,并且QQ.empty
在进程没有崩溃的情况下,我们给予消息第二次到达的机会(1秒远远超过了任何可以被限定为“无限小的时间”的时间)。所以,在这里,我会说,你可以很肯定,你永远不会陷入等待一个空队列。