【问题标题】:Multiprocess inherently shared memory in no longer working on python 3.10 (coming from 3.6)不再在 python 3.10(来自 3.6)上工作的多进程固有共享内存
【发布时间】:2022-01-02 00:08:20
【问题描述】:

我了解有多种技术可以在 python 中的进程之间共享内存和数据结构。这个问题专门针对存在于 python 3.6 中但似乎不再存在于 3.10 中的 python 脚本中的这种固有共享内存。 有谁知道为什么以及是否有可能在 3.10 中恢复此功能?或者我观察到的这种变化是什么?我已经将我的 Mac 升级到 Monterey,它不再支持 python 3.6,所以我不得不升级到 3.9 或 3.10+。

注意:我倾向于在 Mac 上开发并在 Ubuntu 上运行生产。不知道这是否是这里的因素。从历史上看,在 3.6 中,无论操作系统如何,一切都表现相同。

使用以下 python 文件制作一个简单的项目

myLibrary.py

MyDict = {}

test.py

import threading
import time
import multiprocessing

import myLibrary


def InitMyDict():
    myLibrary.MyDict = {'woot': 1, 'sauce': 2}
    print('initialized myLibrary.MyDict to ', myLibrary.MyDict)


def MainLoop():
    numOfSubProcessesToStart = 3
    for i in range(numOfSubProcessesToStart):
        t = threading.Thread(
            target=CoolFeature(),
            args=())
        t.start()

    while True:
        time.sleep(1)


def CoolFeature():
    MyProcess = multiprocessing.Process(
        target=SubProcessFunction,
        args=())
    MyProcess.start()


def SubProcessFunction():
    print('SubProcessFunction: ', myLibrary.MyDict)


if __name__ == '__main__':
    InitMyDict()
    MainLoop()

当我在 3.6 上运行它时,它的行为与 3.10 明显不同。我确实理解子进程不能修改主进程的内存,但是访问之前设置的主进程的数据结构仍然非常方便,而不是将每一个小东西都移动到共享内存中只是为了读取一个简单的字典/int/字符串/等。

Python 3.10 输出:

python3.10 test.py 
initialized myLibrary.MyDict to  {'woot': 1, 'sauce': 2}
SubProcessFunction:  {}
SubProcessFunction:  {}
SubProcessFunction:  {}

Python 3.6 输出:

python3.6 test.py 
initialized myLibrary.MyDict to  {'woot': 1, 'sauce': 2}
SubProcessFunction:  {'woot': 1, 'sauce': 2}
SubProcessFunction:  {'woot': 1, 'sauce': 2}
SubProcessFunction:  {'woot': 1, 'sauce': 2}

观察:

请注意,在 3.6 中,子进程可以查看从主进程设置的值。但是在 3.10 中,子进程看到的是一个空字典。

【问题讨论】:

  • target=CoolFeature() 没有意义。为什么这个测试用例首先需要线程?
  • @o11c 你可以忽略线程。这种行为在没有线程的情况下仍然存在。测试用例根本不需要线程。我试图重现该错误,并试图与我的代码所做的非常相似。
  • 我对@9​​87654327@ 不是特别熟悉,但您是否有可能使用基于spawn 的池而不是基于fork 的池?
  • 这不是“固有共享内存”。 multiprocessing 从未提供过这样的功能。你在 Mac 上吗? Mac 上 3.8 中的默认启动方法发生了变化,这可以解释观察到的差异。
  • @o11c 这可能是正确的。请参阅下面的链接。 “在 3.8 版中更改:在 macOS 上,spawn start 方法现在是默认的。fork start 方法应该被认为是不安全的,因为它可能导致子进程崩溃”。但我想知道这对 Ubuntu vs mac 意味着什么……因为我两者都用。 docs.python.org/3/library/…

标签: python python-3.x multiprocessing shared-memory


【解决方案1】:

简而言之,从 3.8 开始,CPython 在 MacO 上使用 spawn 启动方法。在它使用 fork 方法之前。

在 UNIX 平台上,使用 fork 启动方法,这意味着每个新的 multiprocessing 进程都是在 fork 时父进程的精确副本。

spawn 方法意味着它为每个新的multiprocessing 进程启动一个新的 Python 解释器。根据文档:

子进程只会继承运行进程对象的run()方法所必需的资源。

它会将您的程序导入到这个新的解释器中,因此启动进程等只能在if __name__ == '__main__':-block 内完成!

这意味着您不能指望来自父进程的变量在子进程中可用,除非它们是要导入的模块级常量

所以变化很重要。

可以做什么?

如果所需的信息可以是一个模块级的常量,那将会以最简单的方式解决问题。

如果这是不可能的(例如,因为需要在运行时生成数据),您可以让父级将要共享的信息写入文件。例如。以 JSON 格式并在它开始其他进程之前。然后孩子们可以简单地阅读这个。这可能是下一个最简单的解决方案。

使用multiprocessing.Manager 将允许您在进程之间共享dict。但是,这会产生一定的开销。

或者您可以尝试在创建进程或池之前调用multiprocessing.set_start_method("fork"),看看它是否不会在您的情况下崩溃。这将恢复到 MacO 上的 3.8 之前的方法。但正如 in this bug 所记录的那样,在 MacOs 上使用 fork 方法存在实际问题。 阅读问题表明fork 可能没问题只要你不使用线程

【讨论】:

    猜你喜欢
    • 2020-04-07
    • 2015-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-03
    • 1970-01-01
    • 2012-07-29
    相关资源
    最近更新 更多