CLASS 14 · 2026-04-01 · 14-16 并发
并发
并发意味着多个计算在同一时段内交替或并行执行。它带来性能与响应性的提升,也引入了竞态条件这类全新的 bug 类别。理解并发模型是写出可靠并行程序的前提。
什么是并发?
并发(concurrency):多个计算在同一时段内发生。它们可能真正并行(多核),也可能通过快速交替模拟并行。
并发不同于并行(parallelism):并行强调同时执行以加速,并发更关注程序的结构——多个独立的控制流需要协调。
两种并发模型
共享内存
并发模块通过读写内存中的共享对象来交互。这是最直觉的模型,也是最容易出错的模型——多个线程同时修改同一数据时,就会产生竞态条件。
消息传递
并发模块通过通信信道互相发送消息来交互。每个模块持有自己的私有状态,不直接共享数据。这降低了同步的复杂度,但并不能消除所有 bug。
进程、线程与时间切片
- 进程(process):一个正在运行的程序实例,与其他进程相互隔离,拥有独立的地址空间。
- 线程(thread):程序内部的一个控制流线索。同一进程的线程共享内存。
- 时间切片(time slicing):当线程数超过处理器核心数时,操作系统让每个线程轮流执行一小段时间,模拟并发。
// TypeScript 中的 Worker(类似轻量进程)
const worker = new Worker('./worker.js');
worker.postMessage({ type: 'compute', data: [1, 2, 3] });
worker.onmessage = (e) => console.log('结果', e.data);
共享内存示例:银行账户
经典的银行账户例子:两个线程同时对一个余额做存取操作。如果「读取-修改-写入」三步不是原子的,就可能丢失更新。
// 危险:非原子操作
async function withdraw(balance: { value: number }, amount: number) {
if (balance.value >= amount) {
// ← 另一个线程可能在这里修改 balance
balance.value -= amount;
}
}
交错执行
并发指令可以交错(interleave)——来自不同线程的操作以任意顺序穿插。即使每个线程自身逻辑正确,交错后的整体结果也可能出乎意料。
竞态条件
竞态条件(race condition):程序的正确性取决于多个线程或进程操作的相对时序。
竞态条件是最棘手的并发 bug,因为它的出现是不确定的——同样的输入有时成功、有时失败。
消息传递示例
即便使用消息传递,竞态条件依然可能存在。比如两个客户端同时向服务器请求同一资源,服务器处理消息的顺序会影响最终结果。消息传递让状态管理更清晰,但时序问题并未消失。
并发难以测试和调试
并发 bug 常被称为 海森堡 bug(heisenberg bug):它们是非确定性的,难以复现。当你试图用调试器或日志观察它时,时序可能就变了,bug 随之消失。这正是并发程序需要专门工具和设计原则的原因。
关键要点
- 模型共享内存(读写共享对象)与消息传递(通过信道发送消息)。
- 单位进程是隔离的程序实例,线程是进程内的控制流。
- 时间片线程数超过核心数时,用时间切片模拟并发。
- 竞态正确性依赖于操作相对时序,是并发最典型的 bug。
- 海森堡 bug非确定、难复现,需要专门的设计与测试策略。