为啥带有酸洗的多处理序列化取决于范围?

Posted

技术标签:

【中文标题】为啥带有酸洗的多处理序列化取决于范围?【英文标题】:Why does multiprocessing serialisation with pickling depend on scope?为什么带有酸洗的多处理序列化取决于范围? 【发布时间】:2020-03-07 15:54:34 【问题描述】:

我试图了解并发性一般是如何工作的,在这种情况下,它是如何在 Python 中具体工作的。

我使用inputs 库已经有一段时间了,并且在使用它生成进程时总是不得不“作弊” - 使用subprocess.Popen 执行脚本。今天,我不假思索地将一行代码放在了不同的地方,并成功地生成了一个针对函数的Process。但我不明白为什么它会起作用......

以下代码公开了两个简单的类,一个在 self 中包含对controller 的引用,而另一个没有(并使用模块中声明的全局引用):

import inputs
import multiprocessing
import time

controller = inputs.devices.gamepads[0]


class TestBroken:

    def __init__(self):
        self.controller = controller

    def read(self):
        while True:
            ev = self.controller.read()[0]
            print(ev.code, ev.state)


class TestWorking:

    def read(self):
        while True:
            ev = controller.read()[0]
            print(ev.code, ev.state)


if __name__ == '__main__':
    t = TestWorking()
    # Uncomment the line below to get the errors
    #t = TestBroken()
    multiprocessing.Process(target=t.read).start()
    while True:
        print("I'm alive!")
        time.sleep(1)

取消注释#t = TestBroken()后的错误如下:

Traceback (most recent call last):
  File "C:/Coding/...", line 31, in <module>
    multiprocessing.Process(target=t.read).start()
  File "C:\Python\lib\multiprocessing\process.py", line 121, in start
    self._popen = self._Popen(self)
  File "C:\Python\lib\multiprocessing\context.py", line 224, in _Popen
    return _default_context.get_context().Process._Popen(process_obj)
  File "C:\Python\lib\multiprocessing\context.py", line 326, in _Popen
    return Popen(process_obj)
  File "C:\Python\lib\multiprocessing\popen_spawn_win32.py", line 93, in __init__
    reduction.dump(process_obj, to_child)
  File "C:\Python\lib\multiprocessing\reduction.py", line 60, in dump
    ForkingPickler(file, protocol).dump(obj)
AttributeError: Can't pickle local object 'CDLL.__init__.<locals>._FuncPtr'

我不太明白存储对对象的引用如何使pickle 变得疯狂,同时允许在模块级别存储相同的引用。我恳请您协助揭开这个问题背后的奥秘。

【问题讨论】:

【参考方案1】:

当启动多进程时,父进程的全局变量被复制到创建的多个子进程中。因此,父进程的任何更改都不应反映在子进程的副本上。 TestBroken 类中的代码部分有一个构造函数,它将类变量分配给全局变量,然后尝试读取它。而 TestWorking 类只是创建了一个局部变量 ev 来读取控制器(在全局范围内)。

【讨论】:

这根本没有描述酸洗问题,也没有描述为什么这种情况会起作用。您可以轻松地用一些自定义类而不是inputs 替换全局控制器实例,并成功腌制它。 TestBrokerTestWorking 都可以正常工作。此外,父进程甚至无法更改任何内容,因为如上所述由于酸洗错误而无法创建。

以上是关于为啥带有酸洗的多处理序列化取决于范围?的主要内容,如果未能解决你的问题,请参考以下文章

使用 ORTools 实现自定义酸洗代码

使用ORTools实现自定义酸洗代码

如何在 Python 中解释 Dill 的酸洗跟踪输出? (分析(非)酸洗/(反)序列化瓶颈)

带指针的多处理和 ctypes

为啥我不能在 Python 中反序列化带有单引号的 JSON 字符串?

为啥 UserAuthExtensions.PopulateFromMap(session, jwtPayload) 不能在 ServiceStack.Auth 中正确反序列化带有转义的 json 值