← 返回文章列表

并发模型对比:进程、线程、事件循环与协程怎么选

并发性能

先分清两件事:并发与并行

并发是「同时处理多件事」的能力(结构层面),并行是「同一时刻真的在同时执行」(需要多核)。单核机器上仍可以高并发,但只有多核才有真并行。选模型前先问:任务是 CPU 密集还是 IO 密集。

四种模型对比

模型隔离性开销适合不适合
多进程最强(内存独立)高(内存与启动成本)CPU 密集、需要故障隔离需要频繁共享状态
多线程弱(共享内存)中需要共享数据、阻塞型 IO锁竞争严重的场景
事件循环单线程内协作低大量 IO 等待、连接数高任何 CPU 密集任务
协程单线程或多线程承载很低高并发 IO、写法接近同步同样不适合 CPU 密集

选择顺序

  1. CPU 密集:多进程(或多线程 + 真并行语言),把任务分到多个核;
  2. IO 密集且连接多:事件循环或协程,用少量线程支撑大量等待中的连接;
  3. 需要强隔离(不可信代码、易崩溃模块):多进程;
  4. 需要共享大量状态:多线程,但要控制锁的粒度。

四个常见坑

  • 阻塞调用混进事件循环:一次同步 IO 就会卡住整个循环,所有请求一起变慢;
  • 线程池被打满:下游变慢时线程全在等待,新请求排队,故障被放大;
  • 锁竞争:锁粒度过大等于把并发串行化,应缩小临界区或改用无锁结构;
  • 连接数预估不足:每个连接都有内存与文件描述符成本,需要做上限与超时。

先算一算需要多少并发

用 Little's law:并发数 ≈ 吞吐 × 响应时间 粗略估算。若目标是 1000 QPS、平均响应 200ms,则同时约有 200 个请求在处理中。用这个数字反推线程数、连接池与队列长度,比拍脑袋定数量可靠得多。

反压不能少

任何并发模型都要有反压(backpressure):队列有界、超时可控、超出能力时快速失败。没有反压的系统在压力升高时会把延迟转嫁成内存增长,最终以崩溃收场,而不是优雅降级。

衡量要看分布,不要只看平均

评价并发能力要看 P50 / P95 / P99 与超时率,而不是平均耗时。平均耗时正常但 P99 很差,通常意味着有排队、锁竞争或个别慢请求拖累尾部延迟。

实战场景:三个选型判断

  1. “文件上传偶尔损坏”:UDP 不保证顺序与重传,传文件应走 TCP;要低延迟又要可靠,可在应用层自建确认与重传(如 QUIC)。
  2. “实时语音卡顿、延迟高”:丢一帧好过等重传,媒体流通常选 UDP,并在应用层做丢包隐藏与抖动缓冲。
  3. “连接一多就内存飙高”:每个连接都有成本,需设连接上限、超时与反压,避免“请求堆积”演变成 OOM。

常见问题

协程比线程快吗?协程胜在创建与切换成本低、占用内存小,单核吞吐不一定更高;真正的 CPU 工作仍需靠多核并行。事件循环能不能用多核?可以:一个核跑一个事件循环进程(常见的多 worker 模式),由主进程分发连接。为什么加了线程反而更慢?多半是锁竞争、上下文切换或共享队列成为瓶颈。

死锁与活锁

  • 死锁四条件:互斥、持有并等待、不可抢占、循环等待;打破任意一条即可避免,最常用的是统一加锁顺序以消除循环等待;
  • 活锁:线程都在运行却无法推进,例如双方不断退让重试;解法是引入随机退避而不是固定顺序;
  • 饥饿:低优先级任务长期拿不到资源;需检查调度策略与锁的公平性;
  • 排查手段:定期导出线程栈(如 JVM 的 jstack)观察锁等待链,比事后猜测高效得多。