长连接服务中@profile无效,需手动注入LineProfiler、事件触发内存打点、交叉验证泄漏信号,并警惕GPU张量与协程导致的静默泄漏。

@profile 装饰器在长连接服务里根本不起作用
直接给 handler 函数加 @profile,运行后没任何输出——这是最常卡住的地方。因为 @profile 只在 python -m memory_profiler script.py 启动时才生效,而长连接服务(如 Flask、FastAPI、Tornado)通常用 uvicorn、gunicorn 或自定义 event loop 启动,-m memory_profiler 完全不介入。
必须把监控逻辑“注入”到服务生命周期里,而不是依赖启动方式:
- 用
memory_profiler.LineProfiler手动包装关键 handler 函数,在请求入口处调用lp.enable()/lp.disable() - 避免装饰器写在模块顶层;改用运行时动态 patch,比如在
app.on_startup里注册 profiler - 不要监控整个服务进程——它会混入框架开销;只对具体业务函数(如
handle_upload()、process_message())做LineProfiler实例化 - 如果用了多进程(gunicorn worker),
@profile默认不追踪子进程;得显式加--include-children参数,且确保每个 worker 都加载了 profiler
mprof run 对长连接服务采样间隔太粗、漏掉关键波动
mprof run --interval=0.1 your_app.py 看起来很细,但对长连接服务是误导性的:连接建立后大部分时间空闲,内存增长集中在某个请求处理的几十毫秒内,固定 0.1s 采样大概率错过峰值点,只看到“平缓爬升”的假象。
真正有效的做法是按事件触发打点:
- 在请求开始前记录
psutil.Process().memory_info().rss,处理完再记一次,差值才是该请求真实内存增量 - 对高频请求(如心跳、状态上报),加计数器,每 100 次或每 5 秒 flush 一次内存 delta 到日志文件
- 用
gc.get_objects()定期检查可疑类实例数量,例如:[o for o in gc.get_objects() if isinstance(o, MyWebSocketConnection)],看是否只增不减 - 禁用
mprof的自动采样,改用mprof record+ 自定义信号(如kill -USR1 $PID)手动触发快照,便于在疑似泄漏时刻精准抓取
Increment 为正 ≠ 内存泄漏,长连接场景下尤其容易误判
看到某行 Increment 持续 +2MB,第一反应是“就是这行”,但长连接服务里往往不是:Python 内存池机制会让小对象反复分配/释放时复用底层内存块,rss 不降,但实际 Python 对象已被 GC 回收;memory_profiler 显示的只是 C 层内存变化,不是 Python 对象生命周期。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
必须交叉验证才能下结论:
- 查
gc.garbage是否有残留对象(说明循环引用未被清理) - 用
objgraph.show_growth(limit=10)看哪些类型实例数持续增长(需额外装objgraph) - 对比
Increment和gc.get_count():如果第 0 代 GC 频次明显下降,说明新对象存活率变高,才是真正泄漏信号 - 特别注意闭包捕获和弱引用失效:长连接中常把 connection 对象传进回调闭包,若没用
weakref.ref包一层,就会阻止 GC
GPU 张量和异步协程是长连接服务里最隐蔽的泄漏源
HTTP 服务转 WebSocket 或 gRPC 后,常见两类“静默泄漏”:paddle.Tensor、torch.Tensor 在 CPU/GPU 间搬运时没显式 .detach() 或 .cpu(),导致 GPU 显存持续累积;asyncio 协程挂起时,局部变量(尤其是大 buffer)仍被 frame 对象强引用,直到协程彻底结束。
排查这两类问题不能只看 Python 堆:
- GPU 显存必须用
nvidia-ml-py3单独监控:nvmlDeviceGetMemoryInfo,和memory_profiler输出并列比对 - 协程泄漏要 dump 当前所有 alive task:
asyncio.all_tasks(),检查task.get_coro()的 locals 是否含大对象(如bytearray、list) - PaddlePaddle/PyTorch 用户务必检查
with paddle.no_grad():是否包裹了推理代码;漏掉会导致梯度图意外构建并驻留内存 - 别信
del tensor—— 它只删引用,不释放显存;必须调用paddle.device.synchronize()+paddle.device.reset_max_memory_allocated()主动触发清理
长连接服务的内存泄漏往往不在某一行代码,而在对象生命周期与连接生命周期的错配。你看到的 Increment 是表象,真正要盯的是“谁持有引用”和“何时该释放”。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










