Python多进程是否复制父进程全部模块?为何子进程可访问外部内容?
在Python多进程中子进程为何能访问目标函数外的内容?
好问题!这其实和Python多进程的启动机制以及内存复制逻辑直接相关,咱们一步步掰扯清楚:
子进程是不是复制了父进程的所有内容?
这得分操作系统来看:
- Unix/Linux类系统:Python的
multiprocessing默认用fork机制创建子进程。fork的时候会复制父进程的整个地址空间——包括所有已加载的模块、全局变量、函数定义,甚至已经打开的文件句柄等。不过别担心内存会瞬间翻倍,这里用的是写时复制(Copy-On-Write):只有当子进程或者父进程修改某个内存区域时,才会真正复制一份该区域的内容,平时只是共享内存,效率很高。 - Windows系统:Windows没有fork,所以
multiprocessing用的是spawn机制。简单说就是重新启动一个Python解释器,然后重新导入父进程的整个脚本模块,再执行指定的目标函数。这种情况下,子进程能访问的是模块级别定义的内容(比如全局函数、导入的模块),因为脚本被重新加载了一遍。
为什么子进程能访问目标函数外的导入模块和函数?
拿你提供的代码举例:
from multiprocessing import Process, Pipe def test1(): return "hello" def simpleChildProcess( childPipe ): # simpleChildProcess可以访问test1函数 foo = test1() childPipe.send( foo ) parentPipe, childPipe = Pipe() childProcess = Process( target=simpleChildProcess, args=(childPipe,) ) childProcess.start() print "Pipe Contains: %s" % parentPipe.recv()
这里的test1是定义在模块全局作用域的函数,不管是fork还是spawn模式,子进程都能拿到它:
- 如果是fork模式:子进程直接继承了父进程内存里已经定义好的
test1函数对象,自然能调用; - 如果是spawn模式:子进程启动时会重新执行整个脚本(这时候建议把启动代码放在
if __name__ == '__main__':块里,避免无限创建子进程的坑),所以test1的定义会被重新加载,simpleChildProcess当然能调用它。
同样的道理,你导入的multiprocessing模块也是模块级别的,子进程要么通过fork继承过来,要么在spawn时重新导入,所以也能正常使用。
顺便提个Windows下的小优化:把你的代码改成这样更规范,避免踩坑:
from multiprocessing import Process, Pipe def test1(): return "hello" def simpleChildProcess( childPipe ): foo = test1() childPipe.send( foo ) if __name__ == '__main__': parentPipe, childPipe = Pipe() childProcess = Process( target=simpleChildProcess, args=(childPipe,) ) childProcess.start() print "Pipe Contains: %s" % parentPipe.recv()
额外注意点
- 不是所有内容都能被继承/访问:比如父进程中打开的网络连接、一些非可序列化(picklable)的对象,在子进程中可能无法正常使用,尤其是spawn模式下,传递给子进程的参数必须是能被pickle序列化的;
- 写时复制的局限性:如果父进程在创建子进程后修改了某个全局变量,子进程看不到这个修改(因为修改时已经复制了内存区域),反之亦然。
内容的提问来源于stack exchange,提问作者ThunderJack
相关产品推荐
相关产品推荐

