关于uWSGI多进程下对象id与线程标识相同的技术疑问
使用uWSGI以4进程模式(--processes 4)运行Flask应用时,发现每个进程生成的对象id()值相同,且threading.get_ident()值也一致,日志输出如下:
16:47:09,709|object initiating... obj_id : 139755460182224, pid : 9833, threading.get_ident : 139756071208704|
16:47:09,975|object initiating... obj_id : 139755459113168, pid : 9835, threading.get_ident : 139756071208704|
16:47:09,982|object initiating... obj_id : 139755459113168, pid : 9832, threading.get_ident : 139756071208704|
16:47:10,022|object initiating... obj_id : 139755459113168, pid : 9834, threading.get_ident : 139756071208704|
原本认为进程间内存不共享,基于内存地址的id()值不应相同;虽知晓是相对内存地址,但疑惑为何每次都一致。同时不同进程的线程标识也相同,无法理解uWSGI的多进程机制。
相关代码与启动命令:
temp.py定义类A:
import os import threading import logging logger = logging.getLogger(__name__) handler = logging.StreamHandler() formatter = logging.Formatter("%(asctime)s|%(levelname)s|object|%(message)s|") handler.setFormatter(formatter) logger.setLevel(logging.INFO) logger.addHandler(handler) class A(object): def __init__(self): logger.info(f" object initiating... obj_id : {id(self)} pid : {os.getpid()} thread_id : {threading.get_ident()}")
hello.py创建实例:
import logging from flask import Flask from temp import A app = Flask(__name__) a = A() @app.route("/") def hello_world(): return "<p>Hello, World!</p>" if __name__ == '__main__': app.run()
wsgi.py入口:
from hello import app if __name__ == '__main__': app.run()
- 启动命令:
~/.virtualenvs/testflask/bin/uwsgi --http :5004 --module wsgi:app --logto ./log.log --processes 4 --enable-threads --single-interpreter --master --lazy-apps
1. 对象id()值相同的本质
每个进程拥有完全独立的虚拟地址空间,Python的id()返回的是对象在当前进程虚拟地址空间中的内存地址,而非物理内存地址。
你使用了--lazy-apps参数,这个参数让每个uWSGI worker进程独立加载Flask应用,而非由master进程加载后fork子进程。由于每个worker的应用加载流程完全一致:代码执行顺序、内存分配逻辑完全相同,导致类A的实例在每个进程的虚拟地址空间中被分配到了相同的位置,因此id()值完全一致。
但注意:这些相同的虚拟地址对应不同进程的物理内存,对象本身是完全独立的,修改其中一个进程的对象不会影响其他进程。
2. threading.get_ident()值相同的原因
threading.get_ident()返回的是当前进程内的线程标识,这个标识仅在当前进程的线程环境中有意义,不跨进程。
每个uWSGI worker进程启动后,执行应用初始化(创建A实例)的是该进程的主线程。由于每个worker进程的主线程都是进程启动后的第一个线程,其标识在各自进程内的线程ID序列中都是初始值,因此不同进程的主线程标识会出现相同的情况。
3. uWSGI多进程机制的核心
--master:启动主进程,负责管理worker进程的生命周期(启动、监控、重启),主进程不处理请求。--processes 4:启动4个worker进程,每个worker都是独立的操作系统进程,拥有自己的内存空间、文件描述符,进程间完全隔离,内存不共享。--lazy-apps:禁止master进程预加载应用,改为每个worker进程独立加载应用,避免fork带来的内存复制问题,同时保证每个worker的应用状态独立。
可以通过以下方式验证进程间的内存隔离:
修改hello.py,在路由中打印当前进程的pid和全局对象a的属性:
@app.route("/") def hello_world(): import os import random # 给a添加随机属性 setattr(a, 'rand_val', random.randint(1, 100)) return f"<p>Hello, World! PID: {os.getpid()}, A.rand_val: {a.rand_val}, A.id: {id(a)}</p>"
多次访问路由,会发现不同PID的进程返回的rand_val完全独立,即使id(a)相同,也证明对象是各自进程内的独立实例。
内容的提问来源于stack exchange,提问作者쿨해머

