通过全局命名空间vs作为函数参数共享同步对象

时间:2017-03-11 10:58:37

标签: python python-3.x multiprocessing

如果我需要共享multiprocessing.Queuemultiprocessing.Manager(或任何其他同步原语),那么通过在全局(模块)级别定义它们是否有任何区别,将它们作为参数传递给在不同进程中执行的函数?

例如,我可以想象三种可能的方式可以共享队列:

# works fine on both Windows and Linux
from multiprocessing import Process, Queue

def f(q):
    q.put([42, None, 'hello'])

def main():
    q = Queue()
    p = Process(target=f, args=(q,))
    p.start()
    print(q.get())    # prints "[42, None, 'hello']"
    p.join()

if __name__ == '__main__':
    main()

VS

# works fine on Linux, hangs on Windows
from multiprocessing import Process, Queue
q = Queue()

def f():
    q.put([42, None, 'hello'])

def main():
    p = Process(target=f)
    p.start()
    print(q.get())    # prints "[42, None, 'hello']"
    p.join()

if __name__ == '__main__':
    main()

VS

# works fine on Linux, NameError on Windows
from multiprocessing import Process, Queue

def f():
    q.put([42, None, 'hello'])

def main():
    p = Process(target=f)
    p.start()
    print(q.get())    # prints "[42, None, 'hello']"
    p.join()

if __name__ == '__main__':
    q = Queue()
    main()

哪种方法正确?我从我的实验中猜测它只是第一个,但是想确认它的正式情况(不仅适用于Queue而且适用于Manager和其他类似的对象)。

2 个答案:

答案 0 :(得分:1)

正如programming guidelines

中所述
  

明确地将资源传递给子进程

     

在使用fork start方法的Unix上,子进程可以使用全局资源在父进程中创建的共享资源。但是,最好将对象作为参数传递给子进程的构造函数。

     

除了使代码(可能)与Windows和其他启动方法兼容之外,这还确保只要子进程仍处于活动状态,对象就不会在父进程中进行垃圾回收。如果在父进程中对对象进行垃圾回收时释放某些资源,这可能很重要。

问题是spawn / forkserver(Windows仅支持spawn)在引擎盖下工作的方式。它不是用内存和文件解析器克隆父进程,而是从地面创建一个新进程。然后它加载一个新的Python解释器,传递模块以导入并启动它。这显然意味着您的全局变量将是一个全新的Queue而不是父变量。

另一个含义是,要传递给新进程的对象必须是pickleable,因为它们将通过管道传递。

答案 1 :(得分:0)

只是总结answer from Davin Potts

唯一可移植的解决方案是通过将它们作为参数传递来共享Queue()Manager().*个对象 - 从不作为全局变量。原因是在Windows上,所有全局变量都将通过字面上运行模块重新创建(而不是复制)代码从头开始(very little information实际上是从父进程复制到子进程);所以会创建一个全新的Queue(),当然(没有一些不受欢迎和混乱的魔法),它可能无法连接到父进程中的Queue()

我的理解是,将Queue()等作为参数传递是没有缺点的;我找不到任何人想要使用全局变量的非便携式解决方案的原因,但当然我可能错了。