RCU 与读多写少同步
这页讲的是:有些内核数据结构会被大量读取、偶尔修改,如果每次读都重锁,代价会很高。学这页的重点,是理解 RCU 不是“另一种锁”的语法替代,而是一套围绕读路径尽量轻、更新延后回收的同步思路。
这块是什么
RCU 与读多写少同步,讲的是当很多 CPU 或执行路径都想频繁读取某份共享数据,而写操作相对少时,Linux 怎样用“读者尽量不停下来、更新者分阶段替换、旧对象延后回收”的办法,把并发成本降下来。它常出现在线路长、读多写少、对吞吐敏感的数据路径里。
可以把它理解成:图书馆要更新楼层导览图,不是先把旧图全撤掉再让所有人等着,而是先挂上新版,再等确定所有正在看旧图的人都走开后,才把旧图收走。
它负责什么
让读路径更轻
- 让大量读取共享数据的路径少受重锁影响
- 减少频繁读场景下的同步开销
- 让多核并发读取更容易扩展
- 把成本尽量从读侧挪到更新和回收阶段
分离更新与回收
- 更新时先替换到新版本或新指针
- 旧对象不立刻销毁
- 等确认读者都离开旧视图后再回收
- 降低读者踩到失效对象的风险
支撑高并发共享数据
- 适合读多写少的数据结构
- 常与链表、查找结构、路由与对象发布场景一起出现
- 让“很多人看、少数人改”不必总靠粗锁硬顶
- 把同步设计和生命周期设计绑到一起考虑
为什么它不是“再学一种锁”
| 如果想得太简单 | 会怎样 | 真正关键在哪 |
|---|
| 把 RCU 当成普通互斥锁 | 会忽略它并不主要靠阻止读者进入来保证正确性。 | 它更像是在管理“读者看见什么、旧对象何时才能死”。 |
| 只盯着更新动作 | 容易低估延后回收和宽限期的重要性。 | 很多正确性其实落在“旧对象不能死太早”上。 |
| 以为它适合所有共享数据 | 会把写频繁、修改复杂的场景也硬套进去。 | RCU 更擅长读多写少,而不是通吃所有并发问题。 |
| 以为它解决了全部同步问题 | 可能忽略状态一致性、写写竞争和复合更新仍需别的约束。 | RCU 主要照顾读者存活与可见性,不替代所有并发控制。 |
关键概念
| 概念 | 现在怎么理解 |
|---|
| RCU | 一种偏向读多写少场景的同步思路,让读者轻量读取,更新与回收分阶段完成。 |
| 读侧临界区 | 读者在这段期间看到的对象必须继续有效,不能被过早回收。 |
| 宽限期 | 等待所有旧读者离开旧视图的那段过渡时间。 |
| 发布与替换 | 更新者先把新对象或新版本挂出来,再逐步撤掉旧对象。 |
| 延后回收 | 不是没人引用就立刻 free,而是等安全时机到了再回收。 |
为什么重要
- 它解释了为什么 Linux 里有些高并发路径看起来不像在“先锁住再读”。
- 很多看似只是同步问题的 bug,根子其实是旧对象死得太早或发布时序不对。
- 理解这层后,你会更容易把引用计数、生命周期和并发读路径串起来。
- 它让你看到内核并发优化不只是“上更细的锁”,还会重构对象可见性和回收时机。
常见误解
- 误解一:RCU 就是性能更好的锁。实际上它更像是为读路径重写同步和回收规则。
- 误解二:只要用了 RCU 就不用管对象生命周期。实际上延后回收正是生命周期管理的一部分。
- 误解三:RCU 让并发修改自动安全。实际上写路径的一致性常还需要额外同步。
它不负责什么
- 它不替代所有锁;复杂写操作、状态迁移和互斥仍常要靠别的同步手段。
- 它不保证业务语义一致,只负责让读者看到的对象在那段时间内还活着且可读。
- 它不适合所有场景;写很频繁或更新粒度复杂时,未必是最自然选择。
和其他模块的关系
| 相关模块 | 关系 |
|---|
| 并发控制 | RCU 是并发控制里最容易让人感觉“不是传统锁”的一类关键思路。 |
| kref 与引用计数 | 两者都关心对象何时还能活着,但一个偏共享读视图,一个偏显式持有关系。 |
| 等待队列 / workqueue | 延后回收和异步清理常会在这些后台路径里真正发生。 |
| 网络 / 路由 / 对象发布 | 很多读多写少的数据路径会用类似思路组织对象可见性。 |
读完这页后,你应该能回答
- 为什么 RCU 的核心不只是“同步”,而是“让读者看旧对象也别踩空”?
- 为什么读多写少场景不总想靠每次读都上重锁?
- 为什么延后回收和宽限期是理解 RCU 的关键?
后面适合继续问:RCU 和引用计数最容易混在哪?为什么很多人会把“发布新对象”和“回收旧对象”误当成一步?读侧轻量的代价到底被挪到了哪里?