三者关系
| 单位 | 内存 | 切换成本 | 通信 |
|---|---|---|---|
| 进程 | 独立 | 高(内核态) | IPC / 管道 |
| 线程 | 共享地址空间 | 中 | 共享内存,需加锁 |
| 协程 | 共享线程 | 低(用户态) | 通道 / 变量 |
为什么协程更轻
线程由操作系统调度,上千条就会让上下文切换吃光 CPU;协程在用户态由运行时调度,单线程可挂几万协程,遇到等待(IO)就让出,不占内核资源。
代价与坑
- 共享内存要加锁:线程/协程共用变量,竞态会出诡异 bug;
- 别在协程里算重 CPU:阻塞调度器会卡住同线程其他协程;
- 理解并行与并发:多核才是并行,单核只是并发切换。
一句话选型
CPU 密集用多进程 / 多线程吃满核;IO 密集用协程或异步,用少量线程扛高并发。
并发程序的测试与观测
并发缺陷的难点在于复现,因此测试与观测手段比编码技巧更关键。以下几点在实践中最有价值。
- 把并发点显式化:共享状态、临界区与锁的顺序都应能在代码里一眼看出。若需要读很久才能判断某处是否线程安全,说明设计已经过于隐晦,应重构为更清晰的边界。
- 用注入式调度做测试:把等待与调度做成可注入的依赖,测试时用可控的顺序强制交错,能稳定复现“先检查后使用”这类竞态,而不是靠随机等待碰运气。
- 压测要覆盖突发:并发问题常在流量突增时暴露,压测应包含阶梯上升与瞬间尖峰两种模式,观察队列积压、拒绝率与延迟分位数,而不是只看平均吞吐。
- 观测线程与协程状态:导出线程栈、协程数量与队列长度,并设阈值告警。协程数量持续增长通常意味着阻塞调用混入了异步路径,或任务未能正确结束。
- 明确背压策略:当生产速度超过消费速度时,必须显式选择:限流、丢弃或堆积。没有背压设计的并发系统在压力下会以不可预期的方式失败。
容量与资源的关系
并发度并非越高越好。每个并发单元都占用内存与句柄,且会争抢 CPU。把并发度与资源上限、下游承载能力一起评估,才能得到既不过载也不闲置的取值。
异步代码的调试体验
异步调用栈通常不完整,出错时难以定位调用来源。建议为每个任务携带可追踪的上下文标识,并在日志中一致输出;同时在异常处理里保留原始堆栈,避免经过层层包装后丢失关键信息。这些习惯能显著缩短异步问题的排查时间。