Python multiprocessing spawn机制及父子进程内存状态差异疑问
关于multiprocessing spawn模式的相关说明与问题解答
spawn模式官方定义
父进程会启动一个全新的Python解释器进程,子进程仅会继承运行进程对象run()方法所需的必要资源。具体来说,父进程中不必要的文件描述符和句柄不会被继承,与fork或forkserver相比,使用该方法启动进程的速度要慢得多。支持Unix和Windows平台,是Windows和macOS系统的默认启动方式。
测试案例与运行现象
测试1:全局变量继承测试
import multiprocessing as mp import signal FOO = 10 def foo(): assert FOO == 0 def test1(): global FOO FOO = 0 ctx = mp.get_context("spawn") p = ctx.Process(target=foo, args=()) p.start() p.join() if __name__ == "__main__": test1()
运行结果:子进程触发AssertionError,说明父进程运行时修改的全局变量FOO没有被子进程继承。
测试2:C级信号处理继承测试
import multiprocessing as mp import signal def bar(): assert signal.getsignal(signal.SIGTERM) == signal.SIG_DFL def test2(): orignal = signal.getsignal(signal.SIGTERM) assert orignal == signal.SIG_DFL signal.signal(signal.SIGTERM, signal.SIG_IGN) ctx = mp.get_context("spawn") p = ctx.Process(target=bar, args=()) p.start() p.join() if __name__ == "__main__": test2()
运行结果:子进程触发AssertionError,说明父进程设置的SIG_IGN(C级内核态信号忽略规则)被子进程继承。
测试3:Python级信号处理继承测试
import multiprocessing as mp import signal def bar(): # 断言子进程中没有父进程安装的处理函数 assert signal.getsignal(signal.SIGTERM) == signal.SIG_DFL def dummy(signum, frame): print("dummy", signum, frame) def test3(): orignal = signal.getsignal(signal.SIGTERM) assert orignal == signal.SIG_DFL signal.signal(signal.SIGTERM, dummy) ctx = mp.get_context("spawn") p = ctx.Process(target=bar, args=()) p.start() p.join() if __name__ == "__main__": test3()
运行结果:测试通过,说明父进程注册的Python自定义信号处理函数没有被子进程继承。
问题解答
1. spawn模式的完整执行流程
- 父进程调用
start()启动新进程时,首先会创建一个全新的空白Python解释器进程,不会复制父进程的内存空间 - 父进程将运行目标函数所需的必要信息(目标函数、参数、必要的环境配置等)通过序列化(pickle)传递给子进程
- 子进程启动后首先导入主模块,重新执行主模块的顶层代码获取基础上下文,仅会执行
if __name__ == "__main__"之外的顶层代码,父进程在main块中运行时修改的运行时状态不会被执行 - 子进程反序列化拿到的目标函数和参数,执行对应的逻辑
- 非必要的资源(父进程运行时修改的全局状态、自定义Python级信号处理、未显式传递的文件句柄等)默认不会被继承
2. 为什么spawn模式下父子进程状态有时一致有时不一致?
核心判断标准是该状态属于操作系统内核级继承属性还是Python进程用户态自定义属性:
- 状态一致的场景:如果修改的是操作系统内核层面关联到进程的属性,且该属性在进程创建时默认会被子进程继承,就会出现父子进程状态一致的情况。比如测试2中设置的
signal.SIG_IGN是内核级的信号处理标记,新创建的进程默认会继承父进程的内核信号掩码和默认处理规则,因此子进程能拿到这个设置。 - 状态不一致的场景:如果修改的是Python用户态的内存状态,spawn模式下子进程不会复制父进程的运行时内存,只会重新执行顶层代码初始化,因此父进程运行时修改的全局变量、自定义Python级信号处理函数(本质是Python内存中的函数对象)这类状态都不会被子进程继承,就会出现状态差异。比如测试1中修改的全局变量是Python内存中的数值,测试3中注册的是Python层的自定义函数,都不会被子进程拿到。
内容的提问来源于stack exchange,提问作者Izana
相关产品推荐
相关产品推荐

