Skip to content

约 4 分钟阅读

并发基础:一个服务怎样同时处理很多请求 ​

假设你在维护一个订单服务。用户打开订单详情页,接口要先查订单库,再同时问库存、物流、优惠三个下游服务,最后给响应算一个签名。平时一切正常,到了大促,监控上的平均耗时还是 40 毫秒,客服却收到一堆“页面转半天”的投诉;有人说把线程池从 200 调到 800,也有人说该换成协程。

这些讨论里反复出现的词,进程、线程、协程、并发、并行、锁、线程池、QPS、P99,说的其实是同一件事的不同侧面:一个请求进来以后,由谁来执行,执行时在等什么,和别的请求抢什么,以及排了多久的队。这个小系列跟着订单服务的一个请求,把这些概念逐个放回它们出现的位置。

读之前只需要会写简单的程序,知道什么是函数调用和数据库查询。读完后,你应该能说清楚自己的服务里谁在干活、瓶颈是计算还是等待,能判断线程池该开多大,并且看懂监控上的 P95、P99 为什么比平均值更值得关注。

一个请求经过的地方 ​

订单详情请求从到达到返回经过的环节,以及每一处对应的概念

请求先到达服务,如果所有工作线程都在忙,它就进入队列等待;轮到它时,某个线程(或协程)开始执行。执行过程中,大部分时间在等数据库和下游返回,少部分时间在 CPU 上算签名。多个请求同时修改库存这样的共享数据时,还要靠锁保证结果正确。最后,成千上万个请求的耗时汇总成一条分布,平均值、P50、P99 都是从这条分布里取出来的数。

图上每一处都对应系列中的一篇。它们的顺序就是请求经历的顺序:先认识执行者,再看执行者在忙什么,然后是执行者之间的竞争,最后回到排队和耗时统计。

按问题找入口 ​

如果你是带着具体问题来的,可以直接跳到对应的一篇:

你遇到的问题先读
分不清进程、线程、协程,不知道服务里到底有几个“干活的”01 进程、线程和协程:谁在替请求干活
开了多线程,CPU 计算却没有变快02 并发和并行:等待型任务与计算型任务
线程开得很多,CPU 忙,响应却更慢了03 线程越多越快吗:上下文切换与调度
库存偶尔被扣成负数,或者服务偶尔卡死不动04 共享状态:竞态、锁与死锁
线程池开多大合适,CPU 才用到 70% 延迟就开始涨05 线程池开多大:排队、利用率与 Little 定律
平均耗时很好看,用户却说慢06 平均值会骗人:P50、P95、P99 与长尾

第一次系统学习,按 01 到 06 的顺序读。每篇只回答一个问题,都带一个几分钟就能跑完的实验:先看现象,再讲原因,最后给出在真实服务器上观察和调整的方法。

准备实验环境 ​

实验需要 Linux 内核,因为进程、线程和调度的观察入口(/proc、上下文切换计数)都是 Linux 提供的。最省事的方式是在 Docker 里跑,macOS 和 Windows 上装好 Docker Desktop 即可;直接用一台 Linux 虚拟机也可以。

实验镜像和连接容量系列共用一个。新建一个空目录,放入下面的 Dockerfile:

dockerfile
FROM public.ecr.aws/docker/library/python:3.13-slim
RUN apt-get update && apt-get install -y --no-install-recommends iproute2 openssl \
    && rm -rf /var/lib/apt/lists/*

在这个目录里构建一次:

bash
docker build -t capacity-lab .

之后每篇的实验都是一个 Python 脚本,保存到同一个目录,用 docker run ... capacity-lab python 脚本名 运行。部分实验用 --cpuset-cpus 把容器限制在指定的几个 CPU 上,方便观察“核不够用”时的现象。

实验用 Python 写,是因为它短小、容易读。Python 有一个特殊之处:默认解释器带有全局解释器锁(GIL),同一时刻只有一个线程在执行 Python 代码。第 02 篇会正面讨论它的影响,并用多进程演示真正的并行。这一点在 Java、Go、C++ 里不存在,但其余结论,包括排队、锁、上下文切换和百分位,在任何语言里都成立。

文中给出的实验输出来自 2026 年 10 月的实际运行:Docker Desktop 的 Linux 7.0 内核,宿主机是 Apple M4(4 个性能核和 6 个能效核),虚拟机分到 10 个 CPU,Python 3.13。涉及计算速度的数字在这台机器上波动较大,原因会在对应位置说明;换一台机器,具体数字会不同,但现象和比例关系应该一致。

准备好以后,从执行请求的“人”开始:01 进程、线程和协程:谁在替请求干活。

别急,先让缓存热一下。