一、从一次诡异的传感器数据读取说起上周调试一个工业温控模块遇到了奇怪的现象温度采集线程偶尔会读到“跳变”的异常值比如从25.3℃突然变成-12.7℃。逻辑上看数据写入只在中断服务函数里进行读取则在用户线程中间加了读写锁保护按理说不该出问题。用ftrace抓了调度情况才发现症结所在高温时中断频率飙升读线程频繁被写者阻塞实时性受影响。更麻烦的是某些架构上读写锁的开销比想象中大——特别是那些读多写少的场景锁竞争成了瓶颈。这时候就该请出今天要聊的两位顺序锁Seqlock和RCURead-Copy-Update。它们解决的都是同一个核心问题如何让读操作几乎不受写操作的影响。二、顺序锁为读多写少而生的乐观锁先看顺序锁它的设计思想很巧妙读操作不加锁只检查序列号写操作加锁并更新序列号。读之前和读之后各读一次序列号如果两次值相同且为偶数说明数据一致。// 典型使用模式伪代码示意seqlock_ttemp_lock;inttemperature25;// 读者侧do{seqread_seqbegin(temp_lock);// 记住序列号temptemperature;// 读数据}while(read_seqretry(temp_lock,seq));// 检查序列号是否变化// 写着侧write_seqlock(temp_lock);// 获取写锁temperatureread_sensor();// 更新数据write_sequnlock(temp_lock);// 释放并递增序列号关键点在这里顺序锁允许读操作与写操作并发执行但如果检测到写操作正在进行通过序列号变化读者就重试。这属于“乐观锁”思想——假设冲突很少发生发生时再重试。但有几个坑得注意写者会饿死读者如果写操作非常频繁读者可能反复重试。所以顺序锁只适用于写很少的场景比如配置更新、传感器低频采样。数据不能有指针依赖因为读到的可能是中间状态如果数据包含多个关联字段比如链表可能读到不一致的组合。所以顺序锁保护的数据最好是单一标量或结构体。中断上下文要注意Linux内核里写操作会禁用抢占中断里用要小心。我在那个温控项目里试过顺序锁中断频率低时效果很好但后来采样率提高后重试次数明显增多这时候就得考虑更高级的方案了。三、RCU读操作完全零开销的魔法RCU就更神奇了——读操作完全不需要任何原子操作、内存屏障或锁。它的核心思想是写者先创建新副本修改副本然后原子替换指针最后等待所有老读者退出后回收旧数据。// 经典RCU更新流程以链表删除为例// 1. 读者侧完全无锁rcu_read_lock();// 只是标记进入读侧临界区nodercu_dereference(head-next);// 受保护的指针访问// ... 使用node数据rcu_read_unlock();// 标记退出// 2. 写着侧删除节点oldhead-next;newold-next;rcu_assign_pointer(head-next,new);// 原子替换指针synchronize_rcu();// 等待所有读者退出kfree(old);// 安全释放旧数据RCU的精髓在于“等待”synchronize_rcu()会阻塞直到所有在替换前开始的读操作都完成。这个等待是通过“宽限期”Grace Period实现的内核会跟踪所有CPU上的读侧临界区。几个实战经验别在RCU保护的链表里嵌套另一个RCU链表回收顺序会出问题我在这栽过跟头。rcu_dereference()和rcu_assign_pointer()不是可选的它们包含了必要的内存屏障。用户态也有RCU实现liburcu做高性能服务器时很有用。四、选择困难症什么时候用哪个场景推荐机制理由配置参数更新几秒一次顺序锁实现简单读者几乎无开销路由表、进程列表查询RCU读极频繁写较少需要零开销读取传感器数据高频写入读写锁或原子变量顺序锁会导致读者重试过多小结构体如统计计数顺序锁单一变量无指针依赖个人踩坑建议先明确读写比例写频率超过每秒几十次就别用顺序锁了。RCU的学习曲线较陡先从链表操作开始练手理解宽限期机制。调试RCU问题可以用rcu_read_lock_held()做断言能早点发现锁使用错误。在实时性要求高的读侧RCU是利器——它连内存屏障都不需要确定性更好。五、最后聊点实在的驱动开发里同步机制选型往往比算法优化更影响性能。早年我也喜欢无脑用自旋锁后来在千兆网卡驱动里吃到苦头——锁竞争导致吞吐量卡在600Mbps上不去。换成RCU后读路径彻底无锁性能直接跑满线速。现在我的习惯是新驱动先用读写锁性能测试时看锁竞争情况。如果读侧热点明显就考虑RCU如果是配置类数据改用顺序锁。这种渐进式优化比一开始就追求复杂方案更稳妥。记住同步机制是手段不是目的。最终目标是让数据流动得更顺畅而不是展示锁技巧。有时候重新设计数据流减少共享比换任何锁都有效。