内核中的读写信号量
互斥信号量(struct semaphore
),没有区分数据的读写访问。对于大多数的应用场景,读并发都是远高于写并发的。此场景下,互斥信号量就不是十分适用了。
在Linux内核中,读写信号量(Read-Write Semaphore)是一种用于 多读者-单写者 场景的同步机制。相比互斥信号量,它允许多个读取者并行访问,但写入者需要独占访问。它提供了更高的并发性,相对于普通信号量。
普通信号量主要是用来管理资源池;互斥信号量用于互斥,不区分读者/写者;而读写信号量主要是用于读者/写者的互斥。
读写信号量的基本原理
- 读操作:多个读者可以同时获取读写信号量,这允许多个读者同时访问共享资源。
- 写操作:写者必须独占地获取读写信号量,这意味着在写者写入共享资源时,所有其他读者和写者都必须等待。
数据结构
/* All arch specific implementations share the same struct */
struct rw_semaphore {
atomic_long_t count; // 计数器,管理读/写访问权限
struct list_head wait_list; // 等待队列,存放等待获取信号量的任务
raw_spinlock_t wait_lock; // 保护 wait_list 的自旋锁
#ifdef CONFIG_RWSEM_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* spinner MCS lock 乐观自旋锁,提高性能 */
/*
* Write owner. Used as a speculative check to see
* if the owner is running on the cpu.
*/
struct task_struct *owner; // 记录当前持有写锁的任务
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
字段 | 作用 |
---|---|
count | 核心计数器,决定当前信号量的状态(正数=读者数量,负数=写者占用) |
wait_list | 等待队列,存放等待获取信号量的任务 |
wait_lock | 自旋锁,用于保护 wait_list ,避免竞争 |
osq | 乐观自旋锁,用于优化写者的性能,减少调度延迟 |
owner | 当前持有写锁的任务,用于优化自旋锁 |
<font style="color:rgb(64, 64, 64);">atomic_long_t count</font>
- 作用:这是一个原子变量,用于记录读写信号量的状态。
- 实现原理:
- 正值:表示有多个读者持有锁。值为
<font style="color:rgb(0, 0, 0);">n</font>
表示有<font style="color:rgb(0, 0, 0);">n</font>
个读者。 - 负值:表示有一个写者持有锁。
- 值为
<font style="color:rgb(0, 0, 0);">-1</font>
表示一个写者持有锁,没有任何等待的读者或写者。 - 值为
<font style="color:rgb(0, 0, 0);">-N</font>
(<font style="color:rgb(0, 0, 0);">N > 1</font>
)时,表示有一个写者持有写锁,并且有<font style="color:rgb(0, 0, 0);">N-1</font>
个读者或写者在等待获取锁。
- 值为
- 0:表示锁未被任何读者或写者持有。
- 通过原子操作(如
<font style="color:rgb(64, 64, 64);">atomic_long_read</font>
和<font style="color:rgb(64, 64, 64);">atomic_long_cmpxchg</font>
)来修改和检查状态。
- 正值:表示有多个读者持有锁。值为
<font style="color:rgb(64, 64, 64);">count</font>
的比特位通常分为以下几个部分:
比特位范围 | 名称 | 描述 |
---|---|---|
<font style="color:rgb(64, 64, 64);">[63:32]</font> (高位) | 写锁标志和等待线程数量 | 高位用于表示写锁状态和等待线程的数量。 |
<font style="color:rgb(64, 64, 64);">[31:0]</font> (低位) | 活跃读者数量 | 低位用于表示当前持有读锁的读者数量。 |
<font style="color:rgb(64, 64, 64);">struct list_head wait_list</font>
- 作用:这是一个等待队列,用于存放等待获取锁的线程。
- 实现原理:
- 当线程无法立即获取锁时,会被加入到
<font style="color:rgb(64, 64, 64);">wait_list</font>
中,并进入睡眠状态。 - 当锁被释放时,会从
<font style="color:rgb(64, 64, 64);">wait_list</font>
中唤醒等待的进程。
- 当线程无法立即获取锁时,会被加入到
<font style="color:rgb(64, 64, 64);">raw_spinlock_t wait_lock</font>
- 作用:这是一个自旋锁,用于保护
<font style="color:rgb(64, 64, 64);">wait_list</font>
的并发访问(如添加或移除等待的进程)。 - 实现原理:
- 在修改
<font style="color:rgb(64, 64, 64);">wait_list</font>
时,需要先获取<font style="color:rgb(64, 64, 64);">wait_lock</font>
。
- 在修改
<font style="color:rgb(64, 64, 64);">struct optimistic_spin_queue osq</font>
- 作用:
<font style="color:rgb(0, 0, 0);">osq</font>
是一个乐观自旋队列(Optimistic Spin Queue),用于实现一种自适应的自旋锁机制。 - 实现原理:
- 当线程尝试获取锁时,如果锁被其他线程持有,但持有锁的线程正在运行,当前线程会自旋等待,而不是立即睡眠。自旋时间有限,超时后线程会进入睡眠状态。
- 这种机制可以减少上下文切换的开销,提高性能。
- 这种机制通常用于 CPU 密集型场景,以减少锁的获取延迟。
<font style="color:rgb(64, 64, 64);">struct task_struct *owner</font>
- 作用:记录当前持有写锁的线程。
- 实现原理:
- 用于实现写锁的优先级继承和乐观自旋。用于快速检查写锁的持有者(task)是否正在运行(即写者是否在 CPU 上运行)。如果写锁的持有者正在运行,则当前task自旋忙等待;否则进行睡眠。
- 如果写锁的持有者正在运行,其他线程可能会自旋等待,而不是立即睡眠。
读写信号量主要函数
初始化:初始化读写信号量。
void init_rwsem(struct rw_semaphore *sem);
读操作:
void down_read(struct rw_semaphore *sem);
int down_read_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem);
写操作:
void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
升级和降级: 读写信号量还支持从读锁升级到写锁,以及从写锁降级到读锁。
int down_read_up_write(struct rw_semaphore *sem);
void up_write_down_read(struct rw_semaphore *sem);
读写信号量的工作机制
读锁获取(<font style="color:rgb(64, 64, 64);">down_read</font>
)
基本原理
允许多个读者同时访问共享资源,但如果写者持有锁,读者需要等待。
- 当一个读者尝试获取读锁时,内核会检查
<font style="color:rgb(0, 0, 0);">count</font>
的值:- 如果
<font style="color:rgb(0, 0, 0);">count</font>
为正(即已经有读者持有锁),则<font style="color:rgb(0, 0, 0);">count</font>
加 1,读者直接获取锁。 - 如果
<font style="color:rgb(0, 0, 0);">count</font>
为 0,则表示锁未被占用,读者获取锁并将<font style="color:rgb(0, 0, 0);">count</font>
设为 1。 - 如果
<font style="color:rgb(0, 0, 0);">count</font>
为负(即有一个写者持有锁),则读者需要等待,直到写者释放锁。
- 如果
代码实现
/*
* lock for reading
*/
void __sched down_read(struct rw_semaphore *sem)
{
might_sleep(); // 提示内核当前函数可能会睡眠
rwsem_acquire_read(&sem->dep_map, 0, 0, _RET_IP_); // 记录锁的获取操作,帮助检测死锁和锁的滥用
LOCK_CONTENDED(sem, __down_read_trylock, __down_read);
rwsem_set_reader_owned(sem);
}
EXPORT_SYMBOL(down_read);
<font style="color:rgb(64, 64, 64);">might_sleep()</font>
- 作用:提示内核当前函数可能会睡眠。
- 机制:
- 如果当前代码运行在非睡眠环境(如中断上下文),会触发 调试警告,防止死锁。
- 读写信号量可能导致进程阻塞,因此不能在中断上下文或持有自旋锁的情况下调用。
<font style="color:rgb(64, 64, 64);">rwsem_acquire_read(&sem->dep_map, 0, 0, _RET_IP_)</font>
- 作用:记录读锁的获取操作,用于内核锁调试(Lockdep)。不影响信号量的实际操作。
- 机制:
- 如果启用了
<font style="color:rgb(64, 64, 64);">CONFIG_DEBUG_LOCK_ALLOC</font>
,会记录锁的获取操作,帮助检测死锁和锁的滥用。
- 如果启用了
<font style="color:rgb(64, 64, 64);">LOCK_CONTENDED(sem, __down_read_trylock, __down_read)</font>
- 作用:尝试获取读锁,如果失败则进入慢速路径。
- 参数:
<font style="color:rgb(64, 64, 64);">sem</font>
:读写信号量。<font style="color:rgb(64, 64, 64);">__down_read_trylock</font>
:快速路径函数,尝试无竞争地获取读锁。<font style="color:rgb(64, 64, 64);">__down_read</font>
:慢速路径函数,处理竞争情况。
- 机制:
- 首先调用
<font style="color:rgb(64, 64, 64);">__down_read_trylock</font>
尝试快速获取读锁。 - 如果快速路径失败,调用
<font style="color:rgb(64, 64, 64);">__down_read</font>
进入慢速路径(加入等待队列并睡眠)。
- 首先调用
<font style="color:rgb(64, 64, 64);">rwsem_set_reader_owned(sem)</font>
- 作用:标记当前读锁被读者持有。
- 机制:
- 如果支持读者持有者跟踪(
<font style="color:rgb(64, 64, 64);">CONFIG_RWSEM_READER_OWNED</font>
),会设置相关标志。 - 用于调试和性能分析。
- 如果支持读者持有者跟踪(
快速路径:<font style="color:rgb(64, 64, 64);">__down_read_trylock</font>
static inline int __down_read_trylock(struct rw_semaphore *sem)
{
long tmp;
while ((tmp = atomic_long_read(&sem->count)) >= 0) {
if (tmp == atomic_long_cmpxchg_acquire(&sem->count, tmp,
tmp + RWSEM_ACTIVE_READ_BIAS)) {
return 1;
}
}
return 0;
}
- 作用:尝试无竞争地获取读锁。
- 机制:
- 通过原子操作读取
<font style="color:rgb(64, 64, 64);">count</font>
, 判断是否有写锁被持有。 - 如果没有写锁,通过原子操作增加读计数器
<font style="color:rgb(64, 64, 64);">count</font>
。 - 如果成功,返回
<font style="color:rgb(64, 64, 64);">1</font>
;否则返回<font style="color:rgb(64, 64, 64);">0</font>
。–内核代码其实也有很多待优化之处,此处使用<font style="color:rgb(64, 64, 64);">bool</font>
类型更好。
- 通过原子操作读取
慢速路径:__down_read
__down_read
,比较复杂,暂时可以理解为如下伪代码:
static inline void __down_read(struct rw_semaphore *sem)
{
while (true) {
long count = atomic_long_read(&sem->count);
// 检查是否有写锁被持有
if (count >= 0) {
// 尝试增加读计数器
if (atomic_long_try_cmpxchg(&sem->count, &count, count + 1)) {
break; // 成功获取读锁
}
}
// 如果仍有写锁,继续等待
raw_spin_lock(&sem->wait_lock);
list_add_tail(¤t->wait_entry, &sem->wait_list);
raw_spin_unlock(&sem->wait_lock);
schedule();
}
}
读锁释放(up_read
)
基本原理
主要用于 释放读锁,核心作用是减少读者计数,并在必要时唤醒等待的写者。
代码实现
/*
* release a read lock
*/
void up_read(struct rw_semaphore *sem)
{
rwsem_release(&sem->dep_map, 1, _RET_IP_); // 记录锁的释放操作,用于锁调试(Lockdep)
__up_read(sem); // 释放读锁
}
EXPORT_SYMBOL(up_read);
/*
* unlock after reading
*/
static inline void __up_read(struct rw_semaphore *sem)
{
long tmp;
tmp = atomic_long_dec_return_release(&sem->count);
if (unlikely(tmp < -1 && (tmp & RWSEM_ACTIVE_MASK) == 0))
rwsem_wake(sem);
}
减少读计数器
**<font style="color:rgb(64, 64, 64);">tmp = atomic_long_dec_return_release(&sem->count)</font>**
:- 原子地减少
<font style="color:rgb(64, 64, 64);">sem->count</font>
的值,并返回减少后的值。 <font style="color:rgb(64, 64, 64);">release</font>
语义确保该操作之前的内存访问不会被重排序到该操作之后。<font style="color:rgb(64, 64, 64);">tmp</font>
是减少后的<font style="color:rgb(64, 64, 64);">count</font>
值。
- 原子地减少
检查是否需要唤醒等待线程
**<font style="color:rgb(64, 64, 64);">if (unlikely(tmp < -1 && (tmp & RWSEM_ACTIVE_MASK) == 0))</font>**
:**<font style="color:rgb(64, 64, 64);">tmp < -1</font>**
:- 表示当前有写者持有锁,同时有等待的任务(可能是其他写者或读者)。
**<font style="color:rgb(64, 64, 64);">(tmp & RWSEM_ACTIVE_MASK) == 0</font>**
:- 表示没有活跃的读者。
**<font style="color:rgb(64, 64, 64);">unlikely</font>**
:- 提示编译器该条件为假的可能性较大,优化分支预测。
**<font style="color:rgb(64, 64, 64);">rwsem_wake(sem)</font>**
:- 如果满足条件,调用
<font style="color:rgb(64, 64, 64);">rwsem_wake</font>
唤醒等待队列中的线程。
- 如果满足条件,调用
写锁获取
基本原理
- 当一个写者尝试获取写锁时,内核会检查
<font style="color:rgb(0, 0, 0);">count</font>
的值:- 如果
<font style="color:rgb(0, 0, 0);">count</font>
为 0,则表示锁未被占用,写者获取锁并将<font style="color:rgb(0, 0, 0);">count</font>
设为<font style="color:rgb(0, 0, 0);">-1</font>
。 - 如果
<font style="color:rgb(0, 0, 0);">count</font>
为正(即有读者持有锁),则写者需要等待,直到所有读者释放锁。 - 如果
<font style="color:rgb(0, 0, 0);">count</font>
为负(即已经有写者持有锁),则写者需要等待,直到当前写者释放锁。
- 如果
代码实现
/*
* lock for writing
*/
void __sched down_write(struct rw_semaphore *sem)
{
might_sleep(); // 提示内核当前函数可能会睡眠
rwsem_acquire(&sem->dep_map, 0, 0, _RET_IP_); // 记录锁的获取操作(用于调试)
LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
rwsem_set_owner(sem);
}
EXPORT_SYMBOL(down_write);
down_write
也是先通过快速路径获取锁,如果失败则进入慢速路径。
**<font style="color:rgb(64, 64, 64);">rwsem_set_owner</font>**
:标记当前任务为写锁的持有者,有以下几个作用:
- 跟踪写锁持有者
- 其它任务再尝试获取锁时,可以判断占有写锁的任务是否正在CPU上运行(没有睡眠)。
- 占有写锁的任务是否正在CPU上运行,那么当前尝试获取锁的任务(写者)则通过乐观自旋(optimistic spinning)的方式等待写者释放锁。详情见下图。
-
优先级继承如果内核配置了优先级继承(~~<font style="color:rgb(64, 64, 64);">CONFIG_RWSEM_PRIO_AWARE</font>~~
),~~<font style="color:rgb(64, 64, 64);">rwsem_set_owner(sem)</font>~~
会更新写锁持有者的优先级,以避免优先级反转问题。优先级反转:当一个高优先级任务等待一个低优先级任务持有的锁时,可能会导致高优先级任务被阻塞,低优先级任务执行时间被延长无法及时释放锁(中间有无效的任务切换,浪费CPU资源)。优先级继承:通过将持有锁的低优先级任务的优先级提升到高优先级线程的水平,确保低优先级任务能够尽快释放锁。
- 调式支持
- 如果内核配置了锁调试(
<font style="color:rgb(64, 64, 64);">CONFIG_DEBUG_LOCK_ALLOC</font>
),<font style="color:rgb(64, 64, 64);">rwsem_set_owner(sem)</font>
会记录写锁的持有者信息,帮助检测死锁和锁的滥用。
- 如果内核配置了锁调试(
快速路径
#define RWSEM_UNLOCKED_VALUE 0x00000000L
#define RWSEM_ACTIVE_BIAS 0x00000001L
#define RWSEM_WAITING_BIAS (-RWSEM_ACTIVE_MASK-1)
#define RWSEM_ACTIVE_READ_BIAS RWSEM_ACTIVE_BIAS
#define RWSEM_ACTIVE_WRITE_BIAS (RWSEM_WAITING_BIAS + RWSEM_ACTIVE_BIAS)
static inline int __down_write_trylock(struct rw_semaphore *sem)
{
long tmp;
tmp = atomic_long_cmpxchg_acquire(&sem->count, RWSEM_UNLOCKED_VALUE,
RWSEM_ACTIVE_WRITE_BIAS);
return tmp == RWSEM_UNLOCKED_VALUE;
}
作用
- 尝试无竞争地获取写锁。
- 如果成功,返回
<font style="color:rgb(64, 64, 64);">1</font>
;否则返回<font style="color:rgb(64, 64, 64);">0</font>
。
关键点
**<font style="color:rgb(64, 64, 64);">atomic_long_cmpxchg_acquire</font>**
:- 原子地比较并交换
<font style="color:rgb(64, 64, 64);">sem->count</font>
的值。 - 如果
<font style="color:rgb(64, 64, 64);">sem->count</font>
的当前值等于<font style="color:rgb(64, 64, 64);">RWSEM_UNLOCKED_VALUE</font>
(信号量未被持有),则将其设置为<font style="color:rgb(64, 64, 64);">RWSEM_ACTIVE_WRITE_BIAS</font>
(写者持有信号量)。
- 原子地比较并交换
慢速路径
/*
* lock for writing
*/
static inline void __down_write(struct rw_semaphore *sem)
{
long tmp;
tmp = atomic_long_add_return_acquire(RWSEM_ACTIVE_WRITE_BIAS,
&sem->count);
if (unlikely(tmp != RWSEM_ACTIVE_WRITE_BIAS))
rwsem_down_write_failed(sem);
}
作用
- 尝试获取写锁,如果失败则进入慢速路径。
关键点
**<font style="color:rgb(64, 64, 64);">atomic_long_add_return_acquire</font>**
:- 原子地将
<font style="color:rgb(64, 64, 64);">RWSEM_ACTIVE_WRITE_BIAS</font>
加到<font style="color:rgb(64, 64, 64);">sem->count</font>
上,并返回加后的值。
- 原子地将
**<font style="color:rgb(64, 64, 64);">tmp != RWSEM_ACTIVE_WRITE_BIAS</font>**
:- 如果加后的值不等于
<font style="color:rgb(64, 64, 64);">RWSEM_ACTIVE_WRITE_BIAS</font>
,表示当前存在任务持有信号量,进入慢速路径。
- 如果加后的值不等于
**<font style="color:rgb(64, 64, 64);">rwsem_down_write_failed</font>**
:- 处理写锁获取失败的情况,将当前线程加入等待队列并进入睡眠状态。
- 此函数较复杂,不在这里展开。
- 如果当前有写者持有锁,则利用乐观自旋进行忙等,
<font style="color:rgb(64, 64, 64);">__down_write</font>
-><font style="color:rgb(64, 64, 64);">rwsem_down_write_failed</font>
-><font style="color:rgb(64, 64, 64);">__rwsem_down_write_failed_common</font>
写锁释放
基本原理
- 清除当前写锁的所有者:
- 将
<font style="color:rgb(64, 64, 64);">struct rw_semaphore</font>
中的<font style="color:rgb(64, 64, 64);">owner</font>
成员设置为<font style="color:rgb(64, 64, 64);">NULL</font>
。
- 将
- 清除写锁标志:
- 通过原子操作清除
<font style="color:rgb(64, 64, 64);">count</font>
的写锁标志。
- 通过原子操作清除
- 唤醒等待的线程:
- 从
<font style="color:rgb(64, 64, 64);">wait_list</font>
中唤醒所有等待的读者和写者,让他们重新尝试获取锁。
- 从
代码实现
/*
* release a write lock
*/
void up_write(struct rw_semaphore *sem)
{
rwsem_release(&sem->dep_map, 1, _RET_IP_); // 记录锁的释放操作,用于锁调试(Lockdep)
rwsem_clear_owner(sem); // 设置sem->owner为NULL
__up_write(sem); // 释放写锁
}
EXPORT_SYMBOL(up_write);
/*
* unlock after writing
*/
static inline void __up_write(struct rw_semaphore *sem)
{
if (unlikely(atomic_long_sub_return_release(RWSEM_ACTIVE_WRITE_BIAS,
&sem->count) < 0))
rwsem_wake(sem);
}
**<font style="color:rgb(64, 64, 64);">atomic_long_sub_return_release</font>**
:- 原子地将
<font style="color:rgb(64, 64, 64);">RWSEM_ACTIVE_WRITE_BIAS</font>
从<font style="color:rgb(64, 64, 64);">sem->count</font>
中减去,并返回减后的值。 <font style="color:rgb(64, 64, 64);">release</font>
语义确保该操作之前的内存访问不会被重排序到该操作之后。
- 原子地将
**<font style="color:rgb(64, 64, 64);">tmp < 0</font>**
:- 表示有等待线程(可能是读者或写者)。
**<font style="color:rgb(0, 0, 0);">unlikely(...)</font>**
:- 这是一个编译器提示,表示括号内的条件不常为真。
**<font style="color:rgb(64, 64, 64);">rwsem_wake(sem)</font>**
:- 用于唤醒等待队列中等待锁的进程。当写锁释放后,如果有读者或写者在等待队列中等待,它们会被唤醒,重新尝试获取锁。
- 如果有多个进程在等待,它们会被按顺序唤醒,直到锁被重新获取。
读写信号量的优化
linux 4.12中读写信号量的实现结合了以下机制:
- 乐观自旋:通过
<font style="color:rgb(0, 0, 0);">osq</font>
实现自旋锁机制,减少锁的获取延迟。 - 优先级继承:解决优先级反转问题。
- 公平锁:利用
<font style="color:rgb(0, 0, 0);">wait_list</font>
等待队列实现,按先进先出(FIFO)唤醒等待队列中任务的时候。- 可能会有写者饥饿的情况。
- linux 4.12中没有写着优先或者读者优先机制。
- 可以根据实际使用场景,设计写者优先或者读写优先的机制。
乐观自旋机制
乐观自旋是一种优化机制,用于减少线程睡眠和唤醒的开销。其核心思想是:
- 如果锁的持有者正在运行,当前线程可能会自旋等待,而不是立即睡眠。
- 自旋的时间是有限的,如果超过一定时间仍未获取锁,线程会进入睡眠状态。
在 <font style="color:rgb(64, 64, 64);">struct rw_semaphore</font>
中,乐观自旋通过 <font style="color:rgb(64, 64, 64);">osq</font>
(MCS 锁)和 <font style="color:rgb(64, 64, 64);">owner</font>
字段实现:
<font style="color:rgb(64, 64, 64);">osq</font>
用于管理自旋线程的队列。<font style="color:rgb(64, 64, 64);">owner</font>
用于判断锁的持有者是否正在运行。
优先级继承机制
优先级继承用于解决优先级反转问题。其核心思想是:
- 当高优先级线程因低优先级线程持有锁而阻塞时,低优先级线程会临时继承高优先级线程的优先级。
- 在
<font style="color:rgb(64, 64, 64);">struct rw_semaphore</font>
中,通过<font style="color:rgb(64, 64, 64);">owner</font>
字段记录写锁的持有者,支持优先级继承。
读写信号量的使用场景
读写信号量适用于以下场景:
- 多个读者同时读取共享资源,但只有一个写者可以修改资源。
- 读操作频繁,写操作较少的场景。
例如:
- 文件系统的元数据操作(读多写少)。
- 网络配置的读写操作。
用户空间中的读写信号量
在用户空间中,POSIX线程库(pthread)提供了读写锁(read-write lock),功能类似于内核中的读写信号量。也是通过阻塞方式实现的。
读写锁的基本操作
初始化:
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
pthread_rwlock_init(&rwlock, NULL);
读操作:
pthread_rwlock_rdlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
写操作:
pthread_rwlock_wrlock(&rwlock);
pthread_rwlock_unlock(&rwlock);
尝试获取锁:
pthread_rwlock_tryrdlock(&rwlock);
pthread_rwlock_trywrlock(&rwlock);
销毁:
pthread_rwlock_destroy(&rwlock);
Sample
以下是一个简单的示例,展示了如何在用户空间中使用读写锁:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
void* reader(void* arg) {
pthread_rwlock_rdlock(&rwlock);
printf("Reader %ld: Reading...\n", (long)arg);
// 模拟读取操作
sleep(1);
pthread_rwlock_unlock(&rwlock);
return NULL;
}
void* writer(void* arg) {
pthread_rwlock_wrlock(&rwlock);
printf("Writer %ld: Writing...\n", (long)arg);
// 模拟写入操作
sleep(1);
pthread_rwlock_unlock(&rwlock);
return NULL;
}
int main() {
pthread_t threads[3];
// 创建读线程
for (long i = 0; i < 2; i++) {
pthread_create(&threads[i], NULL, reader, (void*)i);
}
// 创建写线程
pthread_create(&threads[2], NULL, writer, (void*)2);
// 等待线程结束
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
pthread_rwlock_destroy(&rwlock);
return 0;
}
总结
- 内核中的读写信号量 提供了一个高效的同步机制,允许多个读者同时访问共享资源,但写者需要独占访问。
- 用户空间中的读写锁 提供了类似的功能,通过POSIX线程库可以实现读写锁,用于同步多线程访问共享资源。
参考资料
- Professional Linux Kernel Architecture,Wolfgang Mauerer
- Linux内核深度解析,余华兵
- Linux设备驱动开发详解,宋宝华
- linux kernel 4.12