第 7 章 · 04 并发模型:三种取舍 本节摘要:这是 Web 服务器的真正难点。前几节的服务器一次只处理一个连接——一个慢客户端会拖住所有人。本节讲三种并发模型:每连接一线程(简单,连接多时开销大)、线程池(复用线程,可控上限)、事件循环/IO 多路复用(单线程高并发,Nginx 的选择)。理解三者的取舍,你就懂得为什么 Nginx 能扛 C10K(万连接)、为什么传统 Apache 选线程池。 内容来源:基于 Web 服务器并发设计整理的导读。 学习目标 说清三种并发模型:每连接一线程、线程池、事件循环。 解释各自取舍:简单 vs 资源 vs 高并发。 理解 IO 多路复用(epoll/kqueue)如何让单线程处理万连接。
本节摘要:这是 Web 服务器的真正难点。前几节的服务器一次只处理一个连接——一个慢客户端会拖住所有人。本节讲三种并发模型:每连接一线程(简单,连接多时开销大)、线程池(复用线程,可控上限)、事件循环/IO 多路复用(单线程高并发,Nginx 的选择)。理解三者的取舍,你就懂得为什么 Nginx 能扛 C10K(万连接)、为什么传统 Apache 选线程池。
内容来源:基于 Web 服务器并发设计整理的导读。
前几节的服务器是「迭代式」——accept 一个连接,处理完才 accept 下一个。一个慢客户端(或恶意慢攻击)就把服务器卡死。生产服务器必须并发处理多连接。而「怎么并发」是计算机科学里一个经典权衡题,理解它你就理解了现代 Web 服务器(Nginx、Node.js、Go、async Python)的设计根基。
accept → fork/创建新线程处理这条连接 → 主线程继续 accept
简单直观。但每个连接占一个线程,线程有开销(栈内存约 8MB、上下文切换成本)。连接一多(几千),内存与切换开销爆炸。早期 Apache 用这模型。
预先建固定数量(如 100)的工作线程, 放池里 accept → 把连接交给池里空闲线程处理 → 处理完线程回池
线程复用,数量有上限(可控资源)。连接超过线程数时排队等待。避免了「每连接一线程」的爆炸,但仍是「阻塞式」——线程在等 IO 时是浪费的。现代 Apache 默认这模型。
用一个系统调用(epoll/kqueue)同时监视所有连接 fd, 哪个 fd 有数据可读才处理 单线程轮询, 不为每个连接建线程
关键洞察:线程在等 IO 时是浪费的。IO 多路复用让一个线程「同时等很多 fd」,哪个就绪处理哪个,绝不阻塞空等。单线程能处理上万连接(C10K)——因为没建那么多线程,只是轮询 fd。Nginx、Node.js、Redis、async Python 都用这模型。
代价:编程模型复杂(异步/回调/协程),不能写「阻塞式」代码(一个阻塞就卡住整个循环)。
| 模型 | 简单度 | 连接数上限 | 代表 |
|---|---|---|---|
| 每连接一线程 | 最简 | 几百-几千 | 早期 Apache |
| 线程池 | 中 | 几千 | 现代 Apache |
| 事件循环 | 复杂 | 几万-几十万 | Nginx、Node、Redis |
ab(Apache Bench)或 wrk 压测,对比三者的连接数上限。下一节是综合——一个能用的并发静态文件服务器。