-决策树与Django项目中的并发选型实践)
文章目录多线程、多进程、协程——决策树与 Django 项目中的并发选型实践下导入语1 ~ 终极决策树2 ~ Django 并发选型问题一Celery Worker 用多进程还是协程2.1 默认是 prefork多进程2.2 换成 gevent协程池2.3 什么时候用 prefork vs gevent3 ~ Django 并发选型问题二Gunicorn Worker 类型3.1 真实经历4 ~ Django 并发选型问题三异步 View vs 同步 View思考 总结结尾多线程、多进程、协程——决策树与 Django 项目中的并发选型实践下文章简介上篇用三组 benchmark 实测了多线程、多进程、协程的纯 IO / 纯 CPU / 混合负载性能。下篇基于这些数据给出一张可直接使用的决策树——拿到需求先判断瓶颈类型IO or CPU再看数据共享需求最后选模型。深入 Django 项目中的实际选型Celery 任务用多进程还是协程Django View 本身需要并发吗Gunicorn 的 worker 类型sync / gevent / gthread怎么选每个问题都对应一个真实的 Django 优化经历——从 celery worker 从 4 进程改成 gevent 协程池后内存从 2GB 降到 400MB。 个人主页源码骑士❄专栏传送门《Android开发基础》《python基础课程》⭐️热衷从源码视角拆解技术底层原理将复杂架构讲得通俗易懂 源码骑士的简介5年Android Framework系统开发经验曾主导多项系统级性能优化专项技术栈覆盖Android系统全链路Binder/Handler/AMS/WMS/启动流程及Java后端全家桶Spring MyBatis Redis Oracle累计产出原创技术文章100篇文章以源码拆解为特色被读者评价为看一篇胜过啃一周文档导入语上篇的三组 benchmark 给了数据下篇直接给决策树。另外把 Django 项目中和并发模型选择紧密结合的几个实际问题拆开——Celery、Gunicorn worker、异步 View——每选错一次就是一次生产事故。1 ~ 终极决策树拿到性能需求 │ ├─ 到底是慢在哪里先做基准测试 │ │ 别猜——用 cProfile / Debug Toolbar 跑一次再说 │ │ │ ├─ IO 密集型网络 / 磁盘 / 数据库查询占绝大部分 │ │ ├─ 需要和现有同步库Django ORM、requests互操作 │ │ │ └─ → 多线程 ThreadPoolExecutor ✅ │ │ │ │ │ ├─ 全链路可从零用异步库写aiohttp、asyncpg、httpx │ │ │ ├─ 并发量500→ 多线程也可以 │ │ │ └─ 并发量1000→ 协程 asyncio ✅ │ │ │ │ │ └─ 数据量大且不能共享内存 → 多进程 Queue │ │ │ ├─ CPU 密集型纯计算图像处理、数值运算、正则大匹配 │ │ ├─ 计算简单且数据量小 → 多进程池 ProcessPoolExecutor ✅ │ │ ├─ 库是 NumPy/Numba/Cython → 多线程也可以C 层释放 GIL │ │ └─ 计算极端重量 → 分离任务队列Celery 独立 worker │ │ │ └─ 混合负载既有 IO 又有 CPU │ ├─ IO 占比70% → 多线程 ✅ │ ├─ CPU 占比70% → 多进程 ✅ │ └─ IO/CPU 各占一半 → 多线程给 CPU 部分也用线程池2 ~ Django 并发选型问题一Celery Worker 用多进程还是协程2.1 默认是 prefork多进程celery-Amyproject worker--concurrency4# 默认 prefork——4 个独立的进程每个进程有自己的一份内存优点稳定不受 GIL 影响。缺点每个进程加载整个 Django app——4 个进程 4 倍的 Django 初始化内存。2.2 换成 gevent协程池pipinstallgevent celery-Amyproject worker--poolgevent--concurrency500500 个协程共享一个 Django 实例——内存从 2GB 降到 400MB。但要求 Celery 任务本身是 IO 密集型的——如果是 CPU 计算gevent 不会有性能收益。2.3 什么时候用 prefork vs gevent任务类型推荐 pool原因发送邮件、推送通知gevent纯 IO协程最优图片压缩、PDF 生成preforkCPU 密集型协程无收益调用第三方 API大量gevent大量等待协程发挥优势数据库批量写入prefork批量写也可能有 DB 层锁prefork 避免阻塞3 ~ Django 并发选型问题二Gunicorn Worker 类型worker 类型并发模型适合场景sync默认一个 worker 一个请求低并发、CPU 密集型gevent协程——一个 worker 处理成千上万个连接IO 密集型高并发gthread一个 worker 多线程需要和 Django 同步代码互操作的 IO 场景# 高并发 APIIO 型——用 geventgunicorn myproject.wsgi --worker-classgevent--workers4--worker-connections1000# CPU 密集型或对同步性要求严格的——用 syncgunicorn myproject.wsgi--workers43.1 真实经历2022 年把 API 网关从sync换成gevent后单台 2 核 4GB 服务器能承载的并发连接数从 200 提升到 3000。但发现有一个使用了requests同步库的 View 在 gevent 下没有释放控制权——必须换上grequestsgevent 版 requests或给该 View 开单独的路由组。4 ~ Django 并发选型问题三异步 View vs 同步 ViewDjango 3.1 支持异步 View。但 Django ORM 本身不支持异步——await Book.objects.filter()不存在。# ❌ 异步 View 中直接调 ORMasyncdefbook_list(request):booksBook.objects.all()# 同步调用——卡死事件循环returnJsonResponse({books:list(books.values())})# ✅ 用 sync_to_async 包装fromasgiref.syncimportsync_to_asyncasyncdefbook_list(request):booksawaitsync_to_async(list)(Book.objects.all().values())returnJsonResponse({books:books})思考 总结Django 并发选型的三条原则**先确定瓶颈IO/CPU**再选模型。数据驱动——别听别人说多线程没用就放弃多线程。Celery 任务类型决定 worker pool。IO → gevent省内存CPU → prefork稳定。Gunicorn worker class 影响面最大。gevent 提高 IO 并发sync 更稳定——根据业务选。结尾并发模型对比上下篇完结。感谢阅读源码骑士 — 源码级拆解从底层看透技术关注跟博主一起从源码视角深耕底层原理❤️点赞让优质内容被更多人看见⭐收藏核心知识点存好随用随查评论分享你的经验或疑问一起交流一键四连别忘了给博主一键四连️寄语选模型不要听口碑——看实测数据看业务特征。结语决策树放在手边以后拿到并发优化需求就不慌。下篇进入 Redis 实战。一键四连