多进程固有的共享内存不再适用于 python 3.10(来自 3.6)
Multiprocess inherently shared memory in no longer working on python 3.10 (coming from 3.6)
我知道 python 中有多种技术可以在进程之间共享内存和数据结构。这个问题专门针对 python 脚本中固有的共享内存,该脚本存在于 python 3.6 中,但似乎不再存在于 3.10 中。 有谁知道为什么以及是否可以在 3.10 中恢复它?或者我观察到的变化是什么? 我已经将我的 Mac 升级到 Monterey,它不再支持 python 3.6,所以我不得不升级到 3.9或 3.10+.
注意:我倾向于在 Mac 上开发,在 Ubuntu 上生产 运行。不确定这里是否有这个因素。从历史上看,对于 3.6,无论 OS.
,一切都表现相同
使用以下 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 有很大不同。我知道子进程不能修改主进程的内存,但是访问之前设置的主进程的数据结构仍然非常方便,而不是将每一个小东西移动到共享内存中只是为了读取一个简单的dictionary/int/string/etc.
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 中,子进程看到一个空字典。
简而言之,从3.8开始,CPython在MacOs上使用spawn启动方法。在它使用 fork 方法之前。
在 UNIX 平台上,使用 fork 启动方法,这意味着每个新的 multiprocessing
进程都是当时 parent 的精确副本叉子。
spawn 方法意味着它为每个新的 multiprocessing
进程启动一个新的 Python 解释器。根据文档:
The child process will only inherit those resources necessary to run the process object’s run()
method.
它将导入你的程序到这个新的解释器中,所以启动进程等只能在if __name__ == '__main__':
块内完成!
这意味着您不能指望来自 parent 进程的变量在 children、 中可用,除非它们是将被导入的模块级常量 .
所以变化很大。
可以做什么?
如果所需的信息可以是一个module-level常量,那将以最简单的方式解决问题。
如果这不可能(例如因为数据需要在运行时生成),您可以让 parent 将要共享的信息写入文件。例如。以 JSON 格式并在它启动其他进程之前。然后 children 可以简单地读取它。这可能是下一个最简单的解决方案。
使用 multiprocessing.Manager
将允许您在进程之间共享 dict
。但是,这会产生一定的开销。
或者您可以尝试在创建进程或池之前调用 multiprocessing.set_start_method("fork")
,看看它是否不会在您的情况下崩溃。这将恢复到 MacO 上 3.8 之前的方法。但是正如 in this bug 所记录的那样,在 MacO 上使用 fork
方法确实存在问题。
阅读问题表明 fork
可能 没问题 只要您不使用线程 .
我知道 python 中有多种技术可以在进程之间共享内存和数据结构。这个问题专门针对 python 脚本中固有的共享内存,该脚本存在于 python 3.6 中,但似乎不再存在于 3.10 中。 有谁知道为什么以及是否可以在 3.10 中恢复它?或者我观察到的变化是什么? 我已经将我的 Mac 升级到 Monterey,它不再支持 python 3.6,所以我不得不升级到 3.9或 3.10+.
注意:我倾向于在 Mac 上开发,在 Ubuntu 上生产 运行。不确定这里是否有这个因素。从历史上看,对于 3.6,无论 OS.
,一切都表现相同使用以下 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 有很大不同。我知道子进程不能修改主进程的内存,但是访问之前设置的主进程的数据结构仍然非常方便,而不是将每一个小东西移动到共享内存中只是为了读取一个简单的dictionary/int/string/etc.
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 中,子进程看到一个空字典。
简而言之,从3.8开始,CPython在MacOs上使用spawn启动方法。在它使用 fork 方法之前。
在 UNIX 平台上,使用 fork 启动方法,这意味着每个新的 multiprocessing
进程都是当时 parent 的精确副本叉子。
spawn 方法意味着它为每个新的 multiprocessing
进程启动一个新的 Python 解释器。根据文档:
The child process will only inherit those resources necessary to run the process object’s
run()
method.
它将导入你的程序到这个新的解释器中,所以启动进程等只能在if __name__ == '__main__':
块内完成!
这意味着您不能指望来自 parent 进程的变量在 children、 中可用,除非它们是将被导入的模块级常量 .
所以变化很大。
可以做什么?
如果所需的信息可以是一个module-level常量,那将以最简单的方式解决问题。
如果这不可能(例如因为数据需要在运行时生成),您可以让 parent 将要共享的信息写入文件。例如。以 JSON 格式并在它启动其他进程之前。然后 children 可以简单地读取它。这可能是下一个最简单的解决方案。
使用 multiprocessing.Manager
将允许您在进程之间共享 dict
。但是,这会产生一定的开销。
或者您可以尝试在创建进程或池之前调用 multiprocessing.set_start_method("fork")
,看看它是否不会在您的情况下崩溃。这将恢复到 MacO 上 3.8 之前的方法。但是正如 in this bug 所记录的那样,在 MacO 上使用 fork
方法确实存在问题。
阅读问题表明 fork
可能 没问题 只要您不使用线程 .