Python多进程fork模式下资源共享疑问:子进程变量ID为何相同?
先贴出你的测试代码和运行结果:
import multiprocessing as mp class Job: def __init__(self,name): self.name=name class A: def __init__(self,x): self.x=x def run(self,job): self.x+=1 print(id(self.x),self.x,job.name) def mul_process_test(self): j1=Job('hello') j2=Job('world') p1=mp.Process(target=self.run,args=(j1,)) p2=mp.Process(target=self.run,args=(j2,)) p1.start() p2.start() p1.join() p2.join() if __name__ =='__main__': a=A(1) a.mul_process_test() print(id(a.x),a.x)
运行结果:
10919360 2 hello
10919360 2 world
10919328 1
这个现象其实是两个知识点共同作用的结果,咱们慢慢拆解:
1. Linux下fork的写时复制机制与进程独立性
你提到的默认fork模式,在创建子进程时,系统会复制父进程的整个地址空间(但采用写时复制Copy-On-Write策略)——简单说就是,一开始子进程和父进程共享物理内存,但只要任何一方对内存进行写入操作,系统就会为写入的部分创建独立的副本。
你的两个子进程启动时,都会复制父进程里的A实例a,但执行self.x +=1时,因为整数是Python的不可变类型,这一步其实是创建了一个新的整数对象2,再赋值给self.x——这个新对象是在子进程自己的地址空间里生成的,和父进程、另一个子进程的对象完全没关系。
2. Python的小整数缓存池导致id重复
Python为了优化性能,会对**-5到256之间的整数进行缓存**——在同一个进程里,这些整数只会被创建一次,每次引用都是指向同一个对象,所以它们的id()是相同的。
而fork出来的子进程会完全复制父进程的内存布局,包括小整数缓存池在虚拟内存中的位置。所以每个子进程里的整数2,都会被放在虚拟内存的同一个位置(也就是id()返回的那个值)。但要注意:这个id是进程内的虚拟地址,不是物理内存地址!两个子进程的2虽然id看起来一样,但它们是各自进程里的独立对象,物理内存上完全不共享。
至于主进程里的1id和子进程里的2不同,只是因为1和2在小整数池里的虚拟地址本来就不一样而已。
总结
三个进程之间完全没有资源共享,子进程里self.x的id相同只是巧合:因为小整数缓存池在每个fork出来的子进程里的虚拟内存布局一致,导致同一个小整数的虚拟地址(也就是id()返回值)相同,但它们是各自进程的独立对象,修改子进程的self.x不会影响其他进程。
内容的提问来源于stack exchange,提问作者kz28

