读写锁是互斥锁的一种,两者的区别在于:
- 互斥锁:线程之间相互排斥,每次只能有一个线程访问临界资源
- 读写锁:
- 写者(线程)相互排斥,每次只能有一个线程访问临界资源
- 读者(线程)互不影响,可以同时访问临界资源。
在读取比修改频繁的场景下,读写锁要更合适一点。因为如果使用互斥锁,在读取数据的时候,读线程之间会争抢锁,但是使用读写锁时,读线程之间互不影响。
画黑板报就是一个典型的读写者模型,画板报的人只有一个(即写者),但是读黑板报的人有多个(即读者)
目录
5、销毁读写锁:pthread_rwlock_destroy
一、模型中的三种关系
两个角色指的是读者角色和写者角色,三种关系指的是读者和读者、写者和写者、读者和写者。
1、写者和写者
每次画黑板报的都只能有一个,两个人同时画的话,一个人画消防主题,另一个人却在画动物主题,这并不是我们想看到的。
==》写者和写者之间是互斥关系。必须要等一个人画完,才能让下一个画
2、读者和写者
有的时候,你可能认为,画黑板报和读黑板报是可以同时进行的,但是画的过程中,读的人可能无法get到你在画什么,明明你在画龙,读的人却说你在画蛇。这就引起误解了,所以应该等写者写完,读者再读。
另一个角度就是,读者还在读,写者却想把板报给擦了,很显然这就矛盾了,所以我们应该让读者读完,写者再写。
==》读者和写者之间是互斥关系。写者在写的时候,读者不能得到锁;读者在读的时候,写者也不能得到锁。
3、读者和读者
一个人读不会影响另一个人读,这里就不存在什么互斥关系了。
==》读者和读者不会互相影响
二、读写锁加锁/解锁的基本原理
读写锁使用读者的数目作为临界资源,即 int readers = 0;
1、读者加锁/解锁
因为读者和读者之间是没有关系的,所以加锁和解锁是两个独立的过程
加锁 = 读黑板报的人加1
解锁 = 读黑板报的人减1
// 加锁
lock();
readers++;
unlock();
// 解锁
lock();
readers--;
unlock();
2、写者加锁/解锁
写者申请锁的时候,就要看读者数量了,主要有两种情况
- readers != 0:当有读者在的时候,写者就无法申请到锁,此时会进入条件变量等待
- readers == 0:只有当读者为0时,此时写者会被唤醒去争抢锁
// 等待读者线程解锁
while(readers > 0)
{
cond_wait(); // 进入条件变量等待(等到读者数目为0,发送信号将挂起的写者唤醒)
}
lock(); //加锁
modify(); //用来表示写者修改内容的操作
unlock(); //解锁
三、读写锁的相关函数
从上面可以了解到,读锁和写锁本质上维护的是同一份临界资源。读者加锁的时候,读者数目加1;写者加锁的时候,必须要等到没有其他读者以后,才会去争抢锁。pthread库提供了 pthread_rwlock_t 这种结构体类型来表示读写锁。其他读写锁相关函数大致如下:
- pthread_rwlock_init:初始化一个读写锁
- pthread_rwlock_rdlock:读者申请锁
- pthread_rwlock_tryrdlock:非阻塞读锁定
- pthread_rwlock_wrlock:写者申请锁
- pthread_rwlock_trywrlock:非阻塞写锁定
- pthread_rwlock_unlock:解锁读写锁
- pthread_rwlock_destroy:释放读写锁
1、初始化读写锁:pthread_rwlock_init
pthread_rwlock_init 的作用是初始化一个读写锁。
第一个参数rwlock:你要初始化的读写锁。
第二个参数 attr:给读写锁设置属性。设为NULL的话表示使用默认属性。
返回值:成功返回0;失败返回一个错误码
2、申请读者锁:pthread_rwlock_rdlock
pthread_rwlock_rdlock 的作用是申请读者锁,此时读者的数量会加1。
3、申请写者锁:pthread_rwlock_wrlock
pthread_rwlock_wrlock 的作用是申请写者锁,允许申请的条件是,读者的数量为0
4、解锁:pthread_rwlock_unlock
pthread_rwlock_unlock 的作用是为读者 / 写者锁解锁。因为读写锁保护的是同一个临界资源,解锁其实就是让这份临界资源可以被读者 / 写者访问到。
5、销毁读写锁:pthread_rwlock_destroy
pthread_rwlock_destroy 的作用是销毁读写锁。
参数rwlock:你要销毁的读写锁
返回值:成功返回0;失败返回一个错误码
四、读写锁的简单使用
现在有一个读线程,一个写线程,读线程在读数据的时候,写线程无法申请锁;写线程只能趁着读线程休眠的空挡来申请锁。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
pthread_rwlock_t rwlock;
void* read_thread(void* args){
pthread_detach(pthread_self());
while (1)
{
pthread_rwlock_rdlock(&rwlock);
printf("读者[1]正在读内容\n");
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
void* write_thread(void* args){
pthread_detach(pthread_self());
while (1)
{
pthread_rwlock_wrlock(&rwlock);
printf("写者[1]正在写内容\n");
pthread_rwlock_unlock(&rwlock);
// sleep(1);
}
}
int main(){
pthread_t tid1,tid2,tid3,tid4;
pthread_rwlock_init(&rwlock, NULL);
pthread_create(&tid1,NULL,read_thread, NULL);
pthread_create(&tid3,NULL,write_thread, NULL);
while(1){
sleep(1);
}
return 0;
}
五、注意事项
同一时刻,只有一个线程可以获得写锁,但是可以有多个线程获得读锁。
- 读写锁处于写锁状态时,所有要申请读写锁的线程都会被阻塞。
- 读写锁处于读锁状态时,写锁申请会阻塞等待,读锁不会受到影响