2、读写锁(Read-Write Lock)

一、读写锁的概念

读写锁(RWLock,全称 Read-Write Lock)是一种在多线程编程中广泛使用的同步机制。
它允许多个线程同时读取共享资源,但写操作必须独占

也就是说:

  • 读操作之间不互斥
  • 写操作与任何操作(读/写)都互斥

因此,读写锁特别适用于**“读多写少”**的场景,例如配置查询、缓存读取、字典查找等。


二、读写锁的核心工作原理

1)基本规则

  1. 当写锁未被持有时,多个线程可以同时获取读锁;
  2. 当写锁被持有时
    • 新的读锁请求会被阻塞;
    • 其他写锁请求也会被阻塞;
  1. 写锁是独占锁
  2. 读锁是共享锁

形象理解:

“写就像装修,别人都不能进;读就像参观,大家可以一起看。”

2)读写锁的状态转换示意

当前持锁情况

可否再获取读锁

可否再获取写锁

无锁状态

✅ 可以

✅ 可以

已有读锁

✅ 可再读

❌ 写被阻塞

已有写锁

❌ 不可读

❌ 不可写

写锁释放

✅ 可再读

✅ 可再写


三、读写锁的类型

读写锁有三种常见调度策略:

类型

优点

缺点

读优先锁

最大化读的并发性能

写线程可能“饿死”

写优先锁

保证写操作不被长期阻塞

读线程可能“饿死”

公平锁

不偏袒读或写,按队列排队

性能相对较低,但最稳定


四、三种策略的工作方式

1)读优先锁(Read-Priority RWLock)

规则:

  • 只要没有线程持有写锁,任何读线程都能立刻获取读锁;
  • 当有一个读线程持有锁时,新来的读线程仍可进入;
  • 写线程只能在没有任何读线程时才能获得锁。

示意:

读A 获得读锁
写B 请求写锁 → 阻塞
读C 请求读锁 → 成功(因为写B在等,读不受影响)
等A与C都释放后,写B 才能获得锁

问题:
如果读线程源源不断,写线程可能一直被饿死,永远等不到执行机会。


2)写优先锁(Write-Priority RWLock)

规则:

  • 当有写线程等待锁时,新的读线程必须排队;
  • 写线程一旦可以执行,就立即获得写锁;
  • 写完后再唤醒所有等待的读线程。

示意:

读A 获得读锁
写B 请求写锁 → 阻塞
读C 请求读锁 → 被阻塞(因为有写B在等待)
等A释放后,写B优先获得锁
写B释放后,读C 才能获得锁

问题:
若系统中写请求频繁,则读线程可能被“饿死”。


3)公平读写锁(Fair RWLock)

规则:

  • 所有线程(读或写)进入同一个等待队列;
  • 按照**先来先服务(FIFO)**顺序分配锁;
  • 避免任意一方饥饿;
  • 仍然允许多个读线程并发执行(当排队轮到“读批次”时)。

实现上常采用队列 + 条件变量

示意:

队列:读A → 写B → 读C → 写D
执行顺序:
1. 读A进入并发执行(可能多个读一起)
2. 写B进入,阻塞后续读
3. A释放 → B执行 → C执行 → D执行

五、代码示例(基于 pthread 读写锁)

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int shared_data = 0;

// ----------- 读线程函数 -----------
void *reader(void *arg) {
    while (1) {
        pthread_rwlock_rdlock(&rwlock);   // 获取读锁
        printf("Reader %ld read value: %d\n", pthread_self(), shared_data);
        pthread_rwlock_unlock(&rwlock);   // 释放读锁
        usleep(200000);                   // 模拟读取间隔
    }
    return NULL;
}

// ----------- 写线程函数 -----------
void *writer(void *arg) {
    while (1) {
        pthread_rwlock_wrlock(&rwlock);   // 获取写锁
        shared_data++;
        printf("Writer %ld wrote value: %d\n", pthread_self(), shared_data);
        pthread_rwlock_unlock(&rwlock);   // 释放写锁
        sleep(1);                         // 模拟写操作耗时
    }
    return NULL;
}

int main() {
    pthread_t readers[3], writers[2];

    for (int i = 0; i < 3; i++)
        pthread_create(&readers[i], NULL, reader, NULL);

    for (int j = 0; j < 2; j++)
        pthread_create(&writers[j], NULL, writer, NULL);

    for (int i = 0; i < 3; i++)
        pthread_join(readers[i], NULL);

    for (int j = 0; j < 2; j++)
        pthread_join(writers[j], NULL);

    return 0;
}

运行结果(输出示例)

Writer 12345 wrote value: 1
Reader 12346 read value: 1
Reader 12347 read value: 1
Reader 12348 read value: 1
Writer 12345 wrote value: 2
Reader 12347 read value: 2
Reader 12348 read value: 2
...

可见:

  • 多个读线程可以同时打印(共享读锁);
  • 写线程执行时,读线程会暂停,等写完再继续(写锁独占)。

六、底层实现机制

读写锁底层可以基于:

  • 互斥锁(Mutex) 实现:读写状态控制由内核管理;
  • 自旋锁(Spinlock) 实现:适合用户态、高性能读多写少场景(如数据库缓存层)。

大多数操作系统(Linux、Windows)和线程库(pthread、C++ std::shared_mutex)都提供原生读写锁。


七、使用场景举例

场景

锁类型

说明

配置文件读取(读取远多于修改)

读写锁

多线程读取配置

缓存系统(高频读缓存、偶尔更新)

读写锁

读并发高效

日志系统(写为主)

写锁

独占操作

数据库索引更新

写锁

防止并发修改

状态查询接口

读锁

多线程安全读取


八、形象类比

场景

对应锁类型

行为解释

图书馆

读写锁

多人可以同时看书(读锁),但上架新书(写锁)时要清场

厨房

写锁

一个厨师炒菜时,其他人不能同时炒(独占资源)

公共信息栏

读锁

多人可以同时阅读公告,但有人贴新公告时(写锁)必须暂时封板


九、优缺点总结

特性

读写锁优点

读写锁缺点

并发性

多读线程并发访问

写锁期间全部阻塞

性能

读多写少时性能优越

写多时性能不如互斥锁

公平性

可实现公平机制防止饥饿

需要额外队列维护

实现复杂度

较高

调试难度更大


十、总结要点

  1. 读写锁核心思想:读共享、写独占。
  2. 应用场景:读多写少。
  3. 三种调度模式
    • 读优先:性能高但可能饿写;
    • 写优先:公平写但可能饿读;
    • 公平锁:性能略降但无饥饿。
  1. 底层实现:可基于互斥锁或自旋锁;
  2. 选择建议
    • 写频繁:用互斥锁;
    • 读远多于写:用读写锁;
    • 用户态轻量并发:可用自旋型 RWLock。

一句话总结:

读写锁让“看书的人可以进图书馆一起看”,但“贴公告的人要独占公告栏”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值