我是否需要将多处理.Queue实例变量显式传递给子进程在实例方法上执行?

时间:2014-10-06 21:24:46

标签: python multiprocessing python-multiprocessing

在使用Python的multiprocessing模块时,我几乎没有基本问题:

class Someparallelworkerclass(object) :

    def __init__(self):
       self.num_workers = 4
       self.work_queue = multiprocessing.JoinableQueue()
       self.result_queue = multiprocessing.JoinableQueue()

    def someparallellazymethod(self):
       p = multiprocessing.Process(target=self.worktobedone).start()

    def worktobedone(self):
      # get data from work_queue
      # put back result in result queue

是否有必要将work_queueresult_queue作为args传递给Process?答案取决于操作系统吗?更基本的问题是:子进程是否从父进程获得复制(COW)地址空间,因此知道类/类方法的定义?如果是,它如何知道要为IPC共享队列,并且它不应该在子进程中复制work_queueresult_queue?我尝试在线搜索,但我发现的大部分文档都很模糊,并没有详细说明底层究竟发生了什么。

3 个答案:

答案 0 :(得分:8)

在这种情况下,无论您使用什么平台,都没有必要在args参数中包含队列。原因是即使看起来你没有明确地将两个JoinableQueue实例传递给孩子,你实际上是 - 通过self。由于self 明确传递给孩子,并且这两个队列是self的一部分,因此最终会传递给孩子。

在Linux上,这通过os.fork()发生,这意味着multiprocessing.connection.Connection内部用于进程间通信的Queue对象使用的文件描述符是继承的 em>由孩子(未复制)。 Queue的其他部分变为copy-on-write,但没关系; multiprocessing.Queue的设计使得所有需要复制的部分实际上都不需要在两个进程之间保持同步。事实上,许多内部属性会在fork发生后重置:

def _after_fork(self):
    debug('Queue._after_fork()')
    self._notempty = threading.Condition(threading.Lock())
    self._buffer = collections.deque()
    self._thread = None
    self._jointhread = None
    self._joincancelled = False
    self._closed = False
    self._close = None
    self._send = self._writer.send  # _writer is a 
    self._recv = self._reader.recv
    self._poll = self._reader.poll

所以这涵盖了Linux。 Windows怎么样? Windows没有fork,因此需要挑选self将其发送给孩子,其中包括挑选我们的Queues。现在,通常如果你试图挑选multiprocessing.Queue,它就会失败:

>>> import multiprocessing
>>> q = multiprocessing.Queue()
>>> import pickle
>>> pickle.dumps(q)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/pickle.py", line 1374, in dumps
    Pickler(file, protocol).dump(obj)
  File "/usr/local/lib/python2.7/pickle.py", line 224, in dump
    self.save(obj)
  File "/usr/local/lib/python2.7/pickle.py", line 306, in save
    rv = reduce(self.proto)
  File "/usr/local/lib/python2.7/copy_reg.py", line 84, in _reduce_ex
    dict = getstate()
  File "/usr/local/lib/python2.7/multiprocessing/queues.py", line 77, in __getstate__
    assert_spawning(self)
  File "/usr/local/lib/python2.7/multiprocessing/forking.py", line 52, in assert_spawning
    ' through inheritance' % type(self).__name__
RuntimeError: Queue objects should only be shared between processes through inheritance

但这实际上是一种人为限制。在某些情况下,multiprocessing.Queue个对象可以被腌制 - 它们如何被发送到Windows中的子进程?事实上,如果我们看一下实现,我们可以看到:

def __getstate__(self):
    assert_spawning(self)
    return (self._maxsize, self._reader, self._writer,
            self._rlock, self._wlock, self._sem, self._opid)

def __setstate__(self, state):
    (self._maxsize, self._reader, self._writer,
     self._rlock, self._wlock, self._sem, self._opid) = state
    self._after_fork()

__getstate__,在挑选一个实例时调用,其中有一个assert_spawning调用,这可以确保我们在尝试pickle *时实际上正在生成一个进程。 __setstate__,在unpickling时调用,负责调用_after_fork

那么当我们必须腌制时,队列使用的Connection对象是如何保持的?事实证明,有一个multiprocessing子模块正是这样做的 - multiprocessing.reduction。该模块顶部的评论非常明确地说明了这一点:

#
# Module to allow connection and socket objects to be transferred
# between processes
#

在Windows上,模块最终使用Windows提供的DuplicateHandle API来创建子进程“Connection”对象可以使用的重复句柄。因此,虽然每个进程都有自己的句柄,但它们完全相同 - 任何一个进程都会反映在另一个进程上:

  

重复句柄指的是与原始句柄相同的对象。   因此,对象的任何更改都会通过两者进行反映   处理。例如,如果复制文件句柄,则为当前文件   两个手柄的位置始终相同。

*有关assert_spawning

的更多信息,请参阅this answer

答案 1 :(得分:1)

子进程在其关闭中没有队列。它是队列引用不同内存区域的实例。以您想要的方式使用队列时,必须将它们作为args传递给函数。我喜欢的一个解决方案是使用functools.partial来修改你想要的队列的函数,将它们永久地添加到它的闭包中,然后让你启动多个线程以使用相同的IPC通道执行相同的任务。

答案 2 :(得分:-1)

子进程没有获得复制的地址空间。孩子是一个完全独立的python进程,没有任何共享。是的,您必须将队列传递给孩子。执行此操作时,多处理会自动通过IPC处理共享。请参阅https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes