首先让我说我没有使用队列,所以这个问题不是this one的重复,而且我没有使用进程池,所以它不是不是this one的副本。
我有一个Process对象,它使用一个线程工作者池来完成一些任务。为了MCVE,这个任务只是构建一个从0到9的整数列表。这是我的来源:
#!/usr/bin/env python3
from multiprocessing.pool import ThreadPool as Pool
from multiprocessing import Process
from sys import stdout
class Quest():
def __init__(self):
pass
def doIt(self, i):
return i
class Test(Process):
def __init__(self, arg):
super(Test, self).__init__()
self.arg = arg
self.pool = Pool()
def run(self):
quest = Quest()
done = self.pool.map_async(quest.doIt, range(10), error_callback=print)
stdout.flush()
self.arg = [item for item in done.get()]
def __str__(self):
return str(self.arg)
# I tried both with and without this method
def join(self, timeout=None):
self.pool.close()
self.pool.join()
super(Test, self).join(timeout)
test = Test("test")
print(test) # should print 'test' (and does)
test.start()
# this line hangs forever
_ = test.join()
print(test) # should print '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]'
这是我希望我的实际程序要做的非常粗略的模型。正如评论中所指出的那样,问题是Test.join
永远都会挂起。这完全独立于是否在Test类中重写了该方法。它也从不打印任何东西,但是当我发送KeyboardInterrupt
信号时的输出表明问题在于从工人那里得到结果:
test
^CTraceback (most recent call last):
File "./test.py", line 44, in <module>
Process Test-1:
_ = test.join()
File "./test.py", line 34, in join
super(Test, self).join(timeout)
File "/path/to/multiprocessing/process.py", line 124, in join
res = self._popen.wait(timeout)
File "/path/to/multiprocessing/popen_fork.py", line 51, in wait
return self.poll(os.WNOHANG if timeout == 0.0 else 0)
File "/path/to/multiprocessing/popen_fork.py", line 29, in poll
pid, sts = os.waitpid(self.pid, flag)
KeyboardInterrupt
Traceback (most recent call last):
File "/path/to/multiprocessing/process.py", line 258, in _bootstrap
self.run()
File "./test.py", line 25, in run
self.arg = [item for item in done.get()]
File "/path/to/multiprocessing/pool.py", line 638, in get
self.wait(timeout)
File "/path/to/multiprocessing/pool.py", line 635, in wait
self._event.wait(timeout)
File "/path/to/threading.py", line 551, in wait
signaled = self._cond.wait(timeout)
File "/path/to/threading.py", line 295, in wait
waiter.acquire()
KeyboardInterrupt
为什么愚蠢的过程不是愚蠢的退出?工作者唯一做的就是执行一个操作的单个解除引用和函数调用,它应该非常简单。
我忘了提及:如果我将Test
设为threading.Thread
的子类而不是multiprocessing.Process
,则此方法正常。我真的不确定为什么会把它分成两半。
答案 0 :(得分:1)
您的目标是异步执行此操作。为什么不从主进程生成异步子进程worker而不生成子进程(类Test)?结果将在您的主要流程中提供,不需要花哨的东西。如果您选择这样做,可以在这里停止阅读。否则,请继续阅读。
您的连接正在运行,因为有两个独立的池,一个是在您创建流程对象时(主流程的本地),另一个是在您通过调用process.start()来分叉流程时(本地到产生的过程)
例如,这不起作用:
def __init__(self, arg, shared):
super(Test, self).__init__()
self.arg = arg
self.quest = Quest()
self.shared = shared
self.pool = Pool()
def run(self):
iterable = list(range(10))
self.shared.extend(self.pool.map_async(self.quest.doIt, iterable, error_callback=print).get())
print("1" + str(self.shared))
self.pool.close()
然而,这有效:
def __init__(self, arg, shared):
super(Test, self).__init__()
self.arg = arg
self.quest = Quest()
self.shared = shared
def run(self):
pool = Pool()
iterable = list(range(10))
self.shared.extend(pool.map_async(self.quest.doIt, iterable, error_callback=print).get())
print("1" + str(self.shared))
pool.close()
这与以下事实有关:当您生成流程时,流程的整个代码,堆栈和堆段将克隆到流程中,以使主流程和子流程具有单独的上下文。
因此,您在主进程本地创建的池对象上调用join(),并在池上调用close()。然后,在run()中,有另一个池对象在调用start()时被克隆到子进程中,并且该池从未关闭,并且无法以您执行的方式连接。简单地说,您的主进程没有引用子进程中的克隆池对象。
如果我将Test作为threading.Thread的子类,这可以正常工作 多处理。进程。我真的不确定为什么会打破它 一半。
有道理,因为线程与进程的不同之处在于它们具有独立的调用堆栈,但共享其他内存段,因此您在另一个线程中创建的对象所做的任何更新都在主进程(即父进程)中可见这些线程),反之亦然。
解决方法是创建run()函数本地的池对象。关闭子进程上下文中的池对象,并在主进程中加入子进程。这将我们带到了#2 ......
如果有经验丰富的经理()希望对其内脏感兴趣,那就太酷了。但是,以下代码为您提供了预期的行为:
#!/usr/bin/env python3
from multiprocessing.pool import ThreadPool as Pool
from multiprocessing import Process, Manager
from sys import stdout
class Quest():
def __init__(self):
pass
def doIt(self, i):
return i
class Test(Process):
def __init__(self, arg, shared):
super(Test, self).__init__()
self.arg = arg
self.quest = Quest()
self.shared = shared
def run(self):
with Pool() as pool:
iterable = list(range(10))
self.shared.extend(pool.map_async(self.quest.doIt, iterable, error_callback=print).get())
print("1" + str(self.shared)) # can remove, just to make sure we've updated state
def __str__(self):
return str(self.arg)
with Manager() as manager:
res = manager.list()
test = Test("test", res)
print(test) # should print 'test' (and does)
test.start()
test.join()
print("2" + str(res)) # should print '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]'
输出:
rpg711$ python multiprocess_async_join.py
test
1[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]