首页 > 解决方案 > 在多进程中,子进程如何从父进程访问全局变量?

问题描述

import multiprocessing

# list with global scope
result = [100,200]

def square_list(mylist):
    """
    function to square a given list
    """
    global result
    # append squares of mylist to global list result
    for num in mylist:
        result.append(num * num)
    # print global list result
    print("Result(in process p1): {}".format(result))

if __name__ == "__main__":
    # input list
    mylist = [1,2,3,4]

    # creating new process
    p1 = multiprocessing.Process(target=square_list, args=(mylist,))
    # starting process
    p1.start()
    # wait until process is finished
    p1.join()

    # print global result list
    print("Result(in main program): {}".format(result))

在这里,全局变量result可以被新进程中运行的函数访问。既然新进程有自己的python解释器和自己的内存空间,那它怎么能从父进程访问全局变量呢?

注意:我了解队列/管道/管理器/数组/值的概念。这个问题是专门问子进程如何从父进程读取全局变量的?

标签: pythonmultiprocessing

解决方案


正如我在对您的问题的评论中提到的,您应该使用作为附加参数传递给的托管列表square_list

import multiprocessing

def square_list(result, mylist):
    """
    function to square a given list
    """
    # append squares of mylist to global list result
    for num in mylist:
        result.append(num * num)
    # print global list result
    print("Result(in process p1): {}".format(result))

if __name__ == "__main__":
    # input list
    mylist = [1,2,3,4]

    result = multiprocessing.Manager().list([100,200])

    # creating new process
    p1 = multiprocessing.Process(target=square_list, args=(result, mylist))
    # starting process
    p1.start()
    # wait until process is finished
    p1.join()

    # print global result list
    print("Result(in main program): {}".format(result))

印刷:

Result(in process p1): [100, 200, 1, 4, 9, 16]
Result(in main program): [100, 200, 1, 4, 9, 16]

笔记

如果您的子进程(“子进程”)只读取result列表,那么您的代码就可以了。但是当您想要更新列表并将其反映回主进程时,事情会变得有点复杂。

子进程可以通过两种方式更新由主进程创建的对象(我最终会谈到对象实际上是全局变量的问题):

  1. 该对象可以分配在共享内存中,以便两个进程实际上都在访问相同的存储空间,尽管它们通常“存在”在不同的地址空间中。
  2. 该对象是一个“托管”对象,由对代理的引用表示,通过该代理进行所有访问。当通过代理对对象进行更新时,数据实际上是使用套接字或命名管道从一个地址空间传输到另一个地址空间,具体取决于平台和其他考虑因素。因此,这更类似于远程过程调用。

让我们以使用全局变量更新一个简单的共享内存对象为例。为此,我将使用一个简单的multiprocessing.Value实例来创建一个共享整数:

import multiprocessing

v = multiprocessing.Value('i', 1) # initialize to 1

def worker():
    v.value += 10

if __name__ == "__main__":
    p = multiprocessing.Process(target=worker)
    p.start()
    p.join()

    print(v.value)

在 Windows 上,此打印结果111您预期的不同。这是因为在 Windows 上,新进程是使用该spawn方法创建的。这意味着创建了一个新的空地址空间,启动了一个新的 Python 解释器,并从顶部重新执行源代码,并且执行全局范围内的任何代码,但if __name__ == "__main__":块内的代码除外,因为在新创建的进程中__name__不会"__main__"(这是一件好事,否则你会进入一个递归循环,重新创建新的子进程)。

但这意味着子进程刚刚创建了自己的全局变量实例v。因此,要使其正常工作,v不能是全局的,必须作为参数传递给worker.

但是,如果您使用多处理池,则有一种方法。此工具允许您使用特殊的池初始化函数初始化池中的每个进程:

import multiprocessing

# initialize each process (there is only 1) in the pool
def init_pool(shared_v):
    global v
    v = shared_v # v is global

def worker():
    v.value += 10

if __name__ == "__main__":
    v = multiprocessing.Value('i', 1) # I am global

    # create pool of size 1:
    pool = multiprocessing.Pool(1, initializer=init_pool, initargs=(v,))
    pool.apply(worker)

印刷:

11

不幸的是,使用可用的共享内存数据类型来实现一个列表需要一些工作。这就是我推荐使用托管列表的原因:

import multiprocessing

# initialize each process (there is only 1) in the pool
def init_pool(shared_result):
    global result
    result = shared_result # result is global

def square_list(mylist):
    """
    function to square a given list
    """
    # append squares of mylist to global list result
    for num in mylist:
        result.append(num * num)
    # print global list result
    print("Result(in process p1): {}".format(result))

if __name__ == "__main__":
    # input list
    mylist = [1,2,3,4]

    result = multiprocessing.Manager().list([100,200])

    pool = multiprocessing.Pool(1, initializer=init_pool, initargs=(result,))
    pool.apply(square_list, args=(mylist,))

    # print global result list
    print("Result(in main program): {}".format(result))
Result(in process p1): [100, 200, 1, 4, 9, 16]
Result(in main program): [100, 200, 1, 4, 9, 16]

该技术适用于 Windows、Linux 等,即所有平台。

将可更新的全局变量移到if __name__ == '__main__':块内(它们对于主进程仍然是全局的)并使用池初始化函数来使用这些变量初始化池进程。事实上,对于使用 的平台spawn,您应该考虑将所有子流程不需要且创建成本高昂的全局定义移至if __name__ == '__main__':块内。


推荐阅读