Linux 内核里的 RCU:读不加锁,写不阻塞,凭啥?
说到并发控制,第一反应是锁——互斥锁、自旋锁、读写锁。但 Linux 内核里有个数据结构叫
听起来像是 Copy-on-Write,但关键区别在"等旧读者走完"这一步怎么判断。内核不会跟踪每个读者的进出(那等于又加了锁),而是引入了宽限期(grace period)的概念:一个 CPU 如果经历过了一次上下文切换或者一次 idle,就说明它已经离开了临界区——这叫静默状态(quiescent state)。当所有 CPU 都至少走过一次静默状态,宽限期结束,写者回调执行,把旧数据释放掉。
所以 RCU 的代价不是时间,是内存。旧版本的数据在宽限期内一直活着,读者各看各的,写者攒着回调等宽限期。这就解释了为什么 RCU 只适合读多写极少的场景——如果写频繁,内存累积的旧版本会炸。
实际代码里你会看到
RCU 在内核里用得极其广泛——从
#CS
说到并发控制,第一反应是锁——互斥锁、自旋锁、读写锁。但 Linux 内核里有个数据结构叫
struct rcu_head,对应一套完全不同的思路:RCU,Read-Copy-Update。核心想法很简单——读者啥锁都不拿,直接读;写者不原地改数据,而是拷贝一份修改后,等所有旧读者走完,再把指针切过去。听起来像是 Copy-on-Write,但关键区别在"等旧读者走完"这一步怎么判断。内核不会跟踪每个读者的进出(那等于又加了锁),而是引入了宽限期(grace period)的概念:一个 CPU 如果经历过了一次上下文切换或者一次 idle,就说明它已经离开了临界区——这叫静默状态(quiescent state)。当所有 CPU 都至少走过一次静默状态,宽限期结束,写者回调执行,把旧数据释放掉。
所以 RCU 的代价不是时间,是内存。旧版本的数据在宽限期内一直活着,读者各看各的,写者攒着回调等宽限期。这就解释了为什么 RCU 只适合读多写极少的场景——如果写频繁,内存累积的旧版本会炸。
实际代码里你会看到
rcu_read_lock() 和 rcu_read_unlock(),它们在非抢占内核下甚至不生成任何指令,直接编译成空操作,因为抢占禁用本身就是静默状态的边界。真正的开销全在写者一侧:synchronize_rcu() 等宽限期可能要几十毫秒,所以才有 call_rcu() 做异步回调。rcu_urgentRCU 在内核里用得极其广泛——从
hlist 遍历到 pid 查找到路由表更新,基本所有热路径的读端都在用它。Paul McKenney 为此维护了十几年,光论文就写了一堆。有意思的是这个思路不仅限于内核,用户态也有 liburcu,Meta 早期的系统里大量使用。#CS