Python3 Process对象永远不会加入

时间:2018-03-20 18:20:55

标签: python multithreading python-3.x multiprocessing

首先让我说我没有使用队列,所以这个问题不是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,则此方法正常。我真的不确定为什么会把它分成两半。

1 个答案:

答案 0 :(得分:1)

  1. 您的目标是异步执行此操作。为什么不从主进程生成异步子进程worker而不生成子进程(类Test)?结果将在您的主要流程中提供,不需要花哨的东西。如果您选择这样做,可以在这里停止阅读。否则,请继续阅读。

  2. 您的连接正在运行,因为有两个独立的池,一个是在您创建流程对象时(主流程的本地),另一个是在您通过调用process.start()来分叉流程时(本地到产生的过程)

  3. 例如,这不起作用:

    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 ......

    1. 共享状态:有些multiprocessing.Manager()个对象允许进程之间存在某种神奇的进程安全共享状态。似乎管理器不允许重新分配对象引用,这是有意义的,因为如果在子进程中重新分配托管值,当子进程终止时,该进程上下文(代码,堆栈,堆)将消失,并且您的主进程永远不会看到这个赋值(因为它完成了引用子进程上下文的本地对象)。但它可能适用于ctype原始值。
    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]