并发控制
这页讲的是:Linux 内核里很多代码并不是按单线程顺序跑的,CPU、内核线程、中断、软中断可能同时碰同一份数据,因此必须认真处理并发正确性。学这页的关键,不是死记某种锁的实现,而是建立“为什么内核几乎处处都在防止同时乱动”的感觉。
这块是什么
并发控制不是单独一个“业务模块”,而是贯穿整个内核的基础能力。它负责保证:当多个执行路径同时访问同一资源时,数据不会被破坏,状态不会变乱,系统不会因为竞态条件而偶发出错。和应用层不同,内核不仅要处理线程之间的并发,还要处理不同 CPU、不同上下文、硬件事件和后台处理之间的并发。
可以把它理解成:很多人同时改同一张表,系统必须规定“谁先动、谁等一下、谁可以只读、谁必须独占”,否则表很快就乱了。
它负责什么
保护共享数据
- 避免多个执行流同时乱改同一对象
- 确保状态转换有顺序
- 减少竞态条件
- 避免“平时没事,偶尔炸一次”的随机错误
协调不同执行上下文
- 进程上下文
- 中断上下文
- 软中断和延后执行
- 让不同环境下的代码能按各自规则安全协作
平衡正确性与性能
- 减少不必要的等待
- 控制锁竞争
- 兼顾可扩展性
- 避免“修对了但跑得很慢”的另一类失败
为什么内核里的并发更麻烦
| 来源 | 你现在怎么理解 | 为什么会变难 |
|---|
| 多核 CPU | 多个核可以同时执行不同代码路径。 | 不再是“轮流执行”的假象,而是真的同时动同一份状态。 |
| 任务切换 | 同一个任务可能在任意点被切走,另一个任务接着运行。 | 任何共享结构都要考虑切换点带来的时序变化。 |
| 中断 | 硬件事件可能突然打断当前执行流。 | 某些代码不能睡眠,能用的同步手段也随之受限。 |
| 后台工作 | workqueue、软中断、定时器回调等会在别的上下文继续处理事情。 | “看起来一段逻辑”其实可能分散在多个时间点和上下文里完成。 |
关键概念
| 概念 | 现在怎么理解 |
|---|
| 自旋锁 | 短时间等待时常用的锁,拿不到就原地转,适合不能睡眠的场景。 |
| 互斥锁 | 适合进程上下文里较长时间的互斥访问,拿不到可以睡眠等待。 |
| 原子操作 | 由硬件和指令保证某些最小操作不可被打断。 |
| RCU | 一种偏重“读多写少”场景的同步机制,让读取路径更轻量。 |
| 中断上下文 | 某些代码是在响应硬件事件时执行,约束比普通线程更多。 |
拿锁之前,内核大概在想什么
| 问题 | 为什么要先想清楚 |
|---|
| 这个路径能不能睡眠 | 能不能睡眠,直接决定能用互斥锁还是必须用更轻量但更受限的手段。 |
| 竞争会不会很频繁 | 如果大家都在抢同一把锁,正确性虽然保住了,性能却可能崩掉。 |
| 读多还是写多 | 不同访问模式适合不同同步思路,RCU 就是典型例子。 |
| 这个共享状态会跨哪些上下文 | 如果同时被普通线程和中断路径访问,就不能只按“线程之间互斥”去想。 |
为什么重要
- 内核大量代码都共享状态,不处理并发就会出现随机错、偶发崩溃和难复现 bug。
- 多核机器越普遍,并发问题就越不是“边角问题”,而是常态。
- 很多性能瓶颈并不来自算法本身,而是来自锁竞争和同步开销。
- 很多“偶尔才出一次”的系统问题,本质上都是时序问题,而不是逻辑主线写错。
常见误解
- 误解一:并发控制就是加锁。实际上它是在不同上下文、访问模式和性能目标之间做权衡。
- 误解二:只要没崩就说明并发没问题。事实上并发 bug 很多是低概率、延迟触发、难复现的。
- 误解三:并发只是底层细节。实际上文件系统、网络、驱动、内存管理几乎都被它深刻影响。
它不负责什么
- 它不决定谁占用 CPU,那是调度器的工作。
- 它不决定内存放哪儿,但内存结构往往需要靠它安全访问。
- 它不提供文件或网络语义,只负责保证这些模块内部状态一致。
和其他模块的关系
| 相关模块 | 关系 |
|---|
| 调度器 | 任务什么时候切换,会影响锁什么时候被持有和释放。 |
| 内存管理 | 页表、缓存、回收链表等都需要同步保护。 |
| 文件系统 | 目录项、inode、页缓存等共享结构并发很多。 |
| 驱动 / 中断 | 硬件事件和普通执行流可能同时访问设备状态。 |
读完这页后,你应该能回答
- 为什么内核里的并发不只是“多个线程同时跑”这么简单?
- 为什么不同上下文会限制可用的同步方式?
- 为什么并发问题既是正确性问题,也是性能问题?
后面适合继续问:为什么某些上下文里不能睡眠?RCU 和锁的思路差别是什么?锁竞争为什么会变成性能问题?