一、读写锁的概念
读写锁(RWLock,全称 Read-Write Lock)是一种在多线程编程中广泛使用的同步机制。
它允许多个线程同时读取共享资源,但写操作必须独占。
也就是说:
- 读操作之间不互斥;
- 写操作与任何操作(读/写)都互斥。
因此,读写锁特别适用于**“读多写少”**的场景,例如配置查询、缓存读取、字典查找等。
二、读写锁的核心工作原理
1)基本规则
- 当写锁未被持有时,多个线程可以同时获取读锁;
- 当写锁被持有时:
-
- 新的读锁请求会被阻塞;
- 其他写锁请求也会被阻塞;
- 写锁是独占锁;
- 读锁是共享锁。
形象理解:
“写就像装修,别人都不能进;读就像参观,大家可以一起看。”
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)都提供原生读写锁。
七、使用场景举例
|
场景 |
锁类型 |
说明 |
|
配置文件读取(读取远多于修改) |
读写锁 |
多线程读取配置 |
|
缓存系统(高频读缓存、偶尔更新) |
读写锁 |
读并发高效 |
|
日志系统(写为主) |
写锁 |
独占操作 |
|
数据库索引更新 |
写锁 |
防止并发修改 |
|
状态查询接口 |
读锁 |
多线程安全读取 |
八、形象类比
|
场景 |
对应锁类型 |
行为解释 |
|
图书馆 |
读写锁 |
多人可以同时看书(读锁),但上架新书(写锁)时要清场 |
|
厨房 |
写锁 |
一个厨师炒菜时,其他人不能同时炒(独占资源) |
|
公共信息栏 |
读锁 |
多人可以同时阅读公告,但有人贴新公告时(写锁)必须暂时封板 |
九、优缺点总结
|
特性 |
读写锁优点 |
读写锁缺点 |
|
并发性 |
多读线程并发访问 |
写锁期间全部阻塞 |
|
性能 |
读多写少时性能优越 |
写多时性能不如互斥锁 |
|
公平性 |
可实现公平机制防止饥饿 |
需要额外队列维护 |
|
实现复杂度 |
较高 |
调试难度更大 |
十、总结要点
- 读写锁核心思想:读共享、写独占。
- 应用场景:读多写少。
- 三种调度模式:
-
- 读优先:性能高但可能饿写;
- 写优先:公平写但可能饿读;
- 公平锁:性能略降但无饥饿。
- 底层实现:可基于互斥锁或自旋锁;
- 选择建议:
-
- 写频繁:用互斥锁;
- 读远多于写:用读写锁;
- 用户态轻量并发:可用自旋型 RWLock。
一句话总结:
读写锁让“看书的人可以进图书馆一起看”,但“贴公告的人要独占公告栏”。
1154

被折叠的 条评论
为什么被折叠?



