为何使用multiprocessing.Process创建多进程会生成相同Singleton实例
多进程下Singleton实例ID相同现象原因解释
测试环境
uname -a Linux debian 5.10.0-9-amd64 #1 SMP Debian 5.10.70-1 (2021-09-30) x86_64 GNU/Linux python3 --version Python 3.9.2
注意:添加进程锁可让Singleton类生效,这不是本次要讨论的问题,无需涉及进程锁相关内容。
公共代码
1. Singleton类定义
class Singleton(object): def __init__(self): pass @classmethod def instance(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance
2. 进程执行的任务函数
import time,os def task(): print("start the process %d" % os.getpid()) time.sleep(2) obj = Singleton.instance() print(hex(id(obj))) print("end the process %d" % os.getpid())
两种多进程实现对比
实现1:进程池Pool创建
代码:
from multiprocessing.pool import Pool with Pool(processes = 4) as pool: [pool.apply_async(func=task) for item in range(4)] # 本例中pool.apply、pool.map、pool.map_async效果一致,已验证 pool.close() pool.join()
运行结果:
start the process 11986 start the process 11987 start the process 11988 start the process 11989 0x7fd8764e04c0 end the process 11986 0x7fd8764e05b0 end the process 11987 0x7fd8764e0790 end the process 11989 0x7fd8764e06a0 end the process 11988
结果符合预期:各子进程拥有独立内存空间,互不共享,因此生成的Singleton实例id不同。
实现2:直接使用multiprocessing.Process创建
代码:
import multiprocessing for i in range(4): t = multiprocessing.Process(target=task) t.start()
运行结果:
start the process 12012 start the process 12013 start the process 12014 start the process 12015 0x7fb288c21910 0x7fb288c21910 end the process 12014 end the process 12012 0x7fb288c21910 end the process 12013 0x7fb288c21910 end the process 12015
现象原因解释
- 首先明确Python中
id()函数的本质:在Linux系统下,id()返回的是对象的虚拟内存地址,不是物理内存地址。不同进程的虚拟地址空间完全独立,相同的虚拟地址在不同进程中会映射到完全不同的物理内存,互相没有关联。 - 直接使用
multiprocessing.Process创建子进程时,调用start()会触发系统调用fork(),完整复制父进程的地址空间到子进程。由于你是在父进程还未创建Singleton._instance的状态下依次fork四个子进程,四个子进程的初始内存布局完全一致。 - 子进程启动后只执行了
time.sleep(2)(无内存分配操作),之后第一次调用Singleton.instance()初始化实例时,所有子进程的堆内存空闲区域的起始偏移完全相同,因此分配到的虚拟内存地址就会完全一致,才会出现打印的id相同的情况,本质上四个实例是完全独立、互不共享的。 - 进程池Pool的场景下id不同,是因为Pool会一次性提前fork出所有工作进程,子进程启动后会先执行内部IPC通信、任务队列监听相关的初始化操作,这些操作会分配内存,导致每个工作进程的堆内存布局产生差异,后续分配Singleton实例时的虚拟地址自然就不同了。
你可以自行验证独立性:在Singleton的__init__中添加实例变量,每个进程给该变量赋值为当前进程PID后打印,会发现四个进程的实例变量值完全不同,没有共享。
内容的提问来源于stack exchange,提问作者showkey
相关产品推荐
相关产品推荐

