Python 多处理为每个进程创建相同的对象实例

Mar*_*rov 5 python multiprocessing python-multiprocessing python-object

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

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()
Run Code Online (Sandbox Code Playgroud)

结果是:

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>
Run Code Online (Sandbox Code Playgroud)

无论每次初始化都发生在新进程中,对象地址对于所有人来说都是相同的。谁能指出是什么问题。谢谢。

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

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()
Run Code Online (Sandbox Code Playgroud)

这里的输出是:

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
Run Code Online (Sandbox Code Playgroud)

AKX*_*AKX 8

扩展评论和讨论:

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