内核读写信号量

内核中的读写信号量

互斥信号量(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(&current->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线程库可以实现读写锁,用于同步多线程访问共享资源。

参考资料

  1. Professional Linux Kernel Architecture,Wolfgang Mauerer
  2. Linux内核深度解析,余华兵
  3. Linux设备驱动开发详解,宋宝华
  4. linux kernel 4.12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值