多进程中,子进程如何访问父进程的全局变量?

时间:2021-06-25 13:48:42

标签: python multiprocessing

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解释器和自己的内存空间,那么如何从父进程访问全局变量呢?

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

2 个答案:

答案 0 :(得分:1)

正如我在对您的问题的评论中提到的,您应该使用一个托管列表,该列表作为附加参数传递给 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 上,这会打印为 1 而不是您所期望的 11。这是因为在 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__': 块内,这些定义不是子进程需要的,而且创建起来很昂贵。

答案 1 :(得分:0)

进程与线程的关键租户之一是它们不共享内存。有一些东西实际上可以共享内存,但一般来说,对于进程,您应该通过队列、管道等传递消息。

以下是通过队列将返回值传回父级的示例:

import multiprocessing

# list with global scope
result = [100,200] #result is re-created on import here in the child process

def square_list(mylist, ret_q):
    """
    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))
    ret_q.put(result) #send the modified result to the main process

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

    #return queue
    ret_q = multiprocessing.Queue()

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

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