返回列表 发布新帖
查看: 453|回复: 0

Python 并发实战:多进程 vs 多线程全解析

988

主题

0

回帖

833

积分

高级会员

积分
833
发表于 4 天前 | 查看全部 |阅读模式
这几年写 Python 服务端,最常被问到的问题就是:到底用多进程还是多线程搞并发更合适?看似老生常谈,但每次落到具体业务、部署环境、监控手段,结论往往不一样。我的经验是:先认清任务类型(CPU 密集 vs I/O 密集)、再结合部署成本和可观测性做取舍,别被单一指标(QPS、延迟或资源占用)绑架。

先说线程。CPython 的 GIL 注定了多线程在纯 CPU 密集场景下伸展不开,多个线程抢一个解释器锁,往往“动静大、产出小”。但一到 I/O 密集型——网络请求、磁盘读写、数据库访问——线程模型就顺风顺水:上下文切换轻、栈资源占用低,写起来也直观(阻塞式心智模型),配合 concurrent.futures.ThreadPoolExecutor 基本能把吞吐拉起来。缺点是:线程一多,内存碎片化、调试难度和竞态问题会叠加,线上偶发卡死定位成本不低。我的做法是:I/O 密集优先线程,但线程数控制在 CPU 核数的 2-8 倍区间,根据实际 p99 延迟和连接池耗尽告警调参,辅以超时+熔断。想看官方线程池用法,文档在 docs.python.org。

再说进程。多进程能绕开 GIL,把多核吃满,CPU 密集型(压缩、加密、图像处理、科学计算的 Python 层胶水)用它最合适。multiprocessing 的进程池足够好用,崩一个不至于拖垮全局,隔离性也更安全。代价是:进程启动慢、内存占用高、跨进程通信(pickle 序列化)会成为吞吐瓶颈,频繁传大对象更是灾难。工程上我会把任务切成“粗颗粒”批次,减少 IPC 次数;共享大只读数据时用内存映射或在子进程启动时预加载,避免每次拷贝。还有一点,Linux 下优先选“spawn”还是“forkserver”要结合第三方库兼容性测试一遍,否则线上才发现随机崩。

如果是网络服务,我更倾向“三段式”思路:业务是 I/O 密集,用协程/线程处理;热点算子或向量化不上的 CPU 密集段外包给进程池;跨进程通过消息队列或轻量 RPC 通道连接。这样既能把 GIL 的限制降到最小,也把内存成本和排障复杂度控制住。实践里,协程(asyncio)+少量线程常胜于“线程堆叠”,而“进程池只做重活”比“到处多进程”更稳。可读 asyncio 文档在 docs.python.org,进程池接口在 multiprocessing 文档。

还有几个工程化细节常被忽视。第一,优雅退出:线程要有可中断的等待与超时,进程池要在关闭前 drain 队列并捕获子进程异常,否则会留僵尸进程。第二,观测:线程/协程加上任务 ID 埋点,进程侧记录任务批次与序列化耗时;没有指标的并发就是瞎忙。第三,部署:容器里每个进程的内存上限要结合 cgroup,别以为“系统还有内存”就安全;线程模型下要核对 ulimit 的文件描述符数。第四,库选型:数值密集任务优先调用释放 GIL 的原生扩展(如 numpy、lz4、pyarrow 的部分操作),这样即使用线程也能吃到多核。

总结一下:I/O 密集优先线程/协程,追求简单与低切换开销;CPU 密集优先多进程,换取真正的并行与隔离安全。混合架构是大多数中大型服务的实际最优解。别纠结“谁更强”,让基准测试和监控数据说话:用你业务的真实负载,在目标环境里跑一轮 p50/p99 延迟、吞吐、CPU 利用率、内存占用和崩溃恢复时间,再决定站队。链接参考可在 Python 官方文档的 threading、multiprocessing 与 asyncio 章节找到:docs.python.org。
回复 转播

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关灯 在本版发帖
扫一扫添加微信客服
QQ客服返回顶部
快速回复 返回顶部 返回列表