首页 > 解决方案 > Python多处理为每个进程创建相同的对象实例

问题描述

我写了一个简单的例子来说明我到底在敲什么。可能有一些非常简单的解释,我只是想念。

import time
import multiprocessing as mp
import os


class SomeOtherClass:
    def __init__(self):
        self.a = 'b'


class SomeProcessor(mp.Process):
    def __init__(self, queue):
        super().__init__()
        self.queue = queue

    def run(self):
        soc = SomeOtherClass()
        print("PID: ", os.getpid())
        print(soc)

if __name__ == "__main__":
    queue = mp.Queue()

    for n in range(10):
        queue.put(n)

    processes = []

    for proc in range(mp.cpu_count()):
        p = SomeProcessor(queue)
        p.start()
        processes.append(p)

    for p in processes:
        p.join()

结果是:

PID: 11853
<__main__.SomeOtherClass object at 0x7fa637d3f588>
PID: 11854
<__main__.SomeOtherClass object at 0x7fa637d3f588>
PID: 11855
<__main__.SomeOtherClass object at 0x7fa637d3f588>
PID: 11856
<__main__.SomeOtherClass object at 0x7fa637d3f588>

无论每次初始化都发生在新进程中,对象地址都是相同的。任何人都可以指出什么问题。谢谢。

我也想知道这种行为,当我第一次在主进程中初始化同一个对象然后在其上缓存一些值,然后在每个进程上初始化同一个对象时。然后进程继承主进程对象。

import time
import multiprocessing as mp
import os
import random

class SomeOtherClass:

    c = {}

    def get(self, a):
        if a in self.c:
            print('Retrieved cached value ...')
            return self.c[a]

        b = random.randint(1,999)

        self.c[a] = b

        return b


class SomeProcessor(mp.Process):
    def __init__(self, queue):
        super().__init__()
        self.queue = queue

    def run(self):
        pid = os.getpid()
        soc = SomeOtherClass()
        val = soc.get('new')
        print("Value from process {0} is {1}".format(pid, val))

if __name__ == "__main__":
    queue = mp.Queue()

    for n in range(10):
        queue.put(n)

    pid = os.getpid()
    soc = SomeOtherClass()
    val = soc.get('new')
    print("Value from main process {0} is {1}".format(pid, val))

    processes = []

    for proc in range(mp.cpu_count()):
        p = SomeProcessor(queue)
        p.start()
        processes.append(p)

    for p in processes:
        p.join()

这里的输出是:

Value from main process 13052 is 676
Retrieved cached value ...
Value from process 13054 is 676
Retrieved cached value ...
Value from process 13056 is 676
Retrieved cached value ...
Value from process 13057 is 676
Retrieved cached value ...
Value from process 13055 is 676

标签: pythonmultiprocessingpython-multiprocessingpython-object

解决方案


要扩展评论和讨论:

  • 在 Linux 上,multiprocessing默认为forkstart 方法。分叉一个进程意味着子进程将共享父进程数据的写入时复制版本。这就是为什么全局创建的对象在子进程中具有相同的地址。
    • 在 macOS 和 Windows 上,默认启动方法是spawn- 在这种情况下不共享任何对象。
  • 子进程将在写入对象后立即拥有其唯一的对象副本(事实上,在 CPython 内部,当它们甚至读取它们时,由于引用计数器位于对象标头中)。
  • 一个变量定义为
    class SomeClass:
        container = {}
    
    是类级别,而不是实例级别,并且将在 的所有实例之间共享SomeClass。那是,
    a = SomeClass()
    b = SomeClass()
    print(a is b)  # False
    print(a.container is b.container is SomeClass.container)  # True
    a.container["x"] = True
    print("x" in b.container)  # True
    print("x" in SomeClass.container)  # True
    
    由于类的状态被分叉到子进程中,共享的container似乎也是共享的。但是,在子进程中写入容器不会出现在父进程或兄弟进程中。只有某些特殊multiprocessing类型(和某些较低级别的原语)可以跨越进程边界。
  • 为了正确区分container实例和进程,它需要是实例级的:
    class SomeClass:
        def __init__(self):
            self.container = {}
    
    (当然,如果 aSomeClass是全局实例化的,并且一个进程被分叉,那么它在分叉时的状态将在子进程中可用。)

推荐阅读