注意:这篇文章上次更新于533天前,文章内容可能已经过时。
This article was last updated533 days ago, the content may be outdated.
什么是 RCU
RCU (Read-Copy-Update) 是一种高效的同步机制,主要用于解决读多写少场景下的并发访问问题。它是 Linux 内核中广泛使用的同步技术,由 Paul McKenney 在 2001 年引入。RCU 允许多个读者无锁并发访问共享数据,同时写者可以并发修改数据,而不会导致数据不一致。
What is RCU
RCU (Read-Copy-Update) is an efficient synchronization mechanism primarily used to solve concurrency problems in read-heavy, write-light scenarios. It is a synchronization technique widely used in the Linux kernel, introduced by Paul McKenney in 2001. RCU allows multiple readers to access shared data concurrently without locks, while writers can modify the data concurrently without causing data inconsistency.
RCU 的核心思想
RCU 的核心思想可以概括为三个步骤:
- 读取 (Read): 读者通过 RCU 保护的指针访问数据结构,无需获取锁
- 复制 (Copy): 当需要修改数据时,写者先创建数据的一个副本并修改这个副本
- 更新 (Update): 写者通过原子操作更新指向数据的指针,使其指向修改后的副本
RCU 的特点与优势
- 读者零开销: 读操作不需要锁、内存屏障或原子操作,性能接近于无同步的单线程代码
- 读写并发: 读者和写者可以并发执行,没有互斥问题
- 可扩展性: 随着CPU核心数增加,RCU的性能扩展性良好
- 实时性: 实时系统中特别有用,因为读操作不会被阻塞
RCU 在读多写少场景表现优异,读者开销极低,同时允许与写操作并发执行。
RCU 更新过程
整体工作流程
图表组成部分
Reader1 和 Reader2:表示两个不同时间进入的读者线程
Writer:表示执行更新操作的写者线程
Memory:表示内存/共享数据区域
初始状态:
内存中有一个全局指针 ptr 指向数据对象 data_v1
工作流程详解
- 读取阶段:
Reader1 首先调用 rcu_read_lock() 进入读临界区
然后通过 rcu_dereference(ptr) 安全地获取指针副本
接着读取指针指向的数据内容
- 复制阶段:
与此同时,Writer 创建数据对象 data_v1 的副本 data_v2
Writer 修改 data_v2 的内容
此时 Reader1 仍在使用原始数据 data_v1
- 第二读者进入:
Reader2 调用 rcu_read_lock() 进入读临界区
同样通过 rcu_dereference(ptr) 获取指针副本
此时它看到的仍然是原始数据 data_v1
- 指针更新:
Writer 调用 rcu_assign_pointer(ptr, data_v2) 原子地更新指针
此时内存中的 ptr 已经指向新数据 data_v2
但已经获取了旧指针的 Reader1 和 Reader2 仍然可以继续访问 data_v1
- 宽限期开始:
Reader1 调用 rcu_read_unlock() 离开读临界区
Reader2 仍然在使用 data_v1 数据
Writer 调用 synchronize_rcu() 开始等待所有读者完成
- 宽限期结束:
Reader2 调用 rcu_read_unlock() 离开读临界区
所有使用旧数据的读者都已完成操作
Writer 确认宽限期结束
- 安全清理:
Writer 安全地调用 free(data_v1) 释放旧数据
此时不会有任何读者再使用 data_v1,避免了内存安全问题
内存状态
- 初始状态: 所有读者通过全局指针访问原始数据对象
在下图中,全局指针 ptr 指向数据对象 v1,读者通过这个指针访问数据。
- 创建新版本: 写者创建数据的副本并进行修改
- 发布新版本: 写者原子地更新全局指针指向新数据对象
- 等待宽限期: 写者等待所有现有读者完成访问
- 回收旧版本: 旧版本的数据对象被安全释放
RCU API 在 Linux 内核中的使用
Linux 内核中的 RCU API 主要包括:
读者接口
1 | /* 进入 RCU 读临界区 */ |
写者接口
1 | /* 原子地更新 RCU 保护的指针 */ |
使用示例
展开查看示例代码:
1 | struct foo { |
RCU 与其他机制的比较
| 同步机制 | 读开销 | 写开销 | 读写并发 | 可扩展性 | 适用场景 |
|---|---|---|---|---|---|
| RCU | 极低 | 中等 | 支持 | 极好 | 读多写少 |
| 读写锁 | 低 | 高 | 读读并发 | 一般 | 读多写少场景 |
| 自旋锁 | 中 | 中 | 不支持 | 较差 | 短时间保护 |
| 互斥锁 | 高 | 高 | 不支持 | 较差 | 需要阻塞处理场景 |
参考链接
The Core Idea of RCU
The core idea of RCU can be summarized in three steps:
- Read: Readers access the data structure through the RCU-protected pointer without acquiring any locks
- Copy: When data needs to be modified, the writer first creates a copy of the data and modifies that copy
- Update: The writer atomically updates the pointer that points to the data, making it point to the modified copy
Features and Advantages of RCU
- Zero reader overhead: Read operations require no locks, memory barriers, or atomic operations, with performance close to unsynchronized single-threaded code
- Concurrent reads and writes: Readers and writers can execute concurrently with no mutual exclusion issues
- Scalability: RCU scales well as the number of CPU cores increases
- Real-time friendliness: Especially useful in real-time systems, because read operations are never blocked
RCU performs excellently in read-heavy, write-light scenarios, with extremely low reader overhead while allowing concurrent execution with write operations.
The RCU Update Process
Overall Workflow
Diagram Components
Reader1 and Reader2: two reader threads entering at different times
Writer: the writer thread performing the update operation
Memory: the memory/shared data area
Initial state:
A global pointer ptr in memory points to data object data_v1
Detailed Workflow
- Read phase:
Reader1 first calls rcu_read_lock() to enter the read critical section
Then safely obtains a copy of the pointer via rcu_dereference(ptr)
Then reads the data content pointed to by the pointer
- Copy phase:
Meanwhile, the Writer creates a copy of data object data_v1, called data_v2
The Writer modifies the content of data_v2
At this point Reader1 is still using the original data data_v1
- Second reader enters:
Reader2 calls rcu_read_lock() to enter the read critical section
Also obtains a pointer copy via rcu_dereference(ptr)
At this point it still sees the original data data_v1
- Pointer update:
The Writer calls rcu_assign_pointer(ptr, data_v2) to atomically update the pointer
Now ptr in memory already points to the new data data_v2
But Reader1 and Reader2, who have already obtained the old pointer, can still continue to access data_v1
- Grace period begins:
Reader1 calls rcu_read_unlock() to leave the read critical section
Reader2 is still using the data_v1 data
The Writer calls synchronize_rcu() to wait for all readers to finish
- Grace period ends:
Reader2 calls rcu_read_unlock() to leave the read critical section
All readers using the old data have completed their operations
The Writer confirms the grace period has ended
- Safe cleanup:
The Writer safely calls free(data_v1) to release the old data
At this point no reader will use data_v1 anymore, avoiding memory safety issues
Memory States
- Initial state: All readers access the original data object through the global pointer
In the figure below, the global pointer ptr points to data object v1, and readers access data through this pointer.
- Creating a new version: The writer creates a copy of the data and modifies it
- Publishing the new version: The writer atomically updates the global pointer to point to the new data object
- Waiting for the grace period: The writer waits for all existing readers to finish accessing
- Reclaiming the old version: The old version data object is safely released
RCU API Usage in the Linux Kernel
The RCU APIs in the Linux kernel mainly include:
Reader Interfaces
1 | /* Enter an RCU read critical section */ |
Writer Interfaces
1 | /* Atomically update an RCU-protected pointer */ |
Usage Example
Click to expand the example code:
1 | struct foo { |
RCU Compared with Other Mechanisms
| Sync Mechanism | Read Overhead | Write Overhead | Read-Write Concurrency | Scalability | Use Cases |
|---|---|---|---|---|---|
| RCU | Extremely low | Moderate | Supported | Excellent | Read-heavy, write-light |
| Reader-writer lock | Low | High | Concurrent readers | Average | Read-heavy, write-light scenarios |
| Spinlock | Moderate | Moderate | Not supported | Poor | Short critical sections |
| Mutex | High | High | Not supported | Poor | Scenarios requiring blocking |


