文章目录
- 什么是读写锁
- 生产消费模型 VS 读写模型
- 读写锁的pthread库接口
- 读者&&写者模式
- 模拟实现读写锁
- 思路1——用两个锁来实现(读者优先)
- 模拟实现
- 思路2——两个条件变量一个锁(写者优先)
- 模拟实现
可以看看之前写的文章线程里面有关于锁和生产消费者模型的介绍
什么是读写锁
生产消费模型 VS 读写模型
对于生产者和消费者模型的概念图👇
那么我们读写锁源自读者写着模型,和生产者和消费者模型十分类似👇
两个的模型实际上是一模一样的,我们还是通过“三二一”来深入了解一下读者写者模型
- 三种关系
- 读者-读者:没有关系,共享公共资源
- 读者-写者:互斥、同步,因为读的时候发生了写或写的时候来读,读取到的信息都是错误的!!
- 写者-写者:互斥,临界资源中当有一个写者时其他所有人(不论你是读者还是写者)都进不来
- 两者角色:写者和读者
- 一个场所:临界区
生产者和写者
两个是一模样的,都是向临界区输入数据的
读者和消费者
消费者会从临界区中拿走数据(修改临界区) 而读者不会(可以理解为对临界区表现为只读)
读写锁的pthread库接口
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); /* 销毁RW lock */
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr); /* 初始化RW lock */
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; /* 直接赋值方式初始化RW lock */
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); /* 取得读锁,进入read-mode */
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); /* 尝试取得读锁,失败立即返回 */
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); /* 取得写锁,进入write-mode */
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); /* 尝试取得写锁,失败立即返回 */
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); /* 释放读/写锁 */
这些接口与普通锁的接口类似就不一一介绍了,但是我们要明确的是不管你是读者和写者都是对一把读写锁加锁,不同身份的人通过不同的接口给读写锁加锁或解锁
- 如果你是读者,你就用
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
这个接口加锁,解锁以此类推 - 如果你是写者,你就用
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
这个接口加锁,解锁以此类推
读者&&写者模式
关于读写锁要知道读写锁的两个模式:
read_mode
: 再此模式下,第一个读者取得了读写锁就进入该模式,所有写者都会被阻塞,而读者可以申请到锁,当最后一个读者释放锁,退出该模式write_mode
:当一个写者取得读写锁就进入该模式之,所有读者和写者均被阻塞,当申请锁的写者释放锁之后退出该模式
模拟实现读写锁
读写锁的实现的思路最重要的是,不管你的身份是读者还是写者,一定要搞清楚目前临界区域的状态!
我们把状态分为三种:
- 读模式
- 写模式
- 初始状态:既不是读模式,也不是写模式。
思路1——用两个锁来实现(读者优先)
实现思路:两把锁,一把我们叫读锁、一把叫写锁。读者走读锁的门,写者走写锁的门
-
读者申请锁 和 释放锁
- 第一个读者:先申请读锁进入临界区,这时他要申请写锁(如果成功这时正式进入读模式),这样写者就就无法进入临界区达到读者写者互斥。但是读者走的时候要释放读锁,否则其他读者进不来
- 中间的读者:除了申请写锁(如果申请写锁就会造成死锁),其他和上面一模一样
- 最后一个读者:除了最后一步释放锁的时候要把写锁归还,其他和上面一样 -
写者申请锁 和 释放锁:就是直接申请写锁,释放就是直接释放写锁。
思考:这样的设计有什么缺点?
这样的设计实际上是读者优先,读者只要申请到锁,写者必须等待所有读者读完,很容易造成饥饿问题!思路二就设计一个写者优先的读写锁。
模拟实现
代码仓库version1
#include <pthread.h>
// 使用两个mutex来实现读写锁
namespace sht
{
class sht_rwlock
{
private:
pthread_mutex_t m1; // 用来锁读者 目的是为了保护read_num这个临界资源
pthread_mutex_t m2; // 用来锁写者 用来控制读者之间的互斥
int read_num = 0; // 记录读者的数量
int cur_state = 0;
// 0 : 表示没有上锁
// 1 :当前在读模式
// -1 :当前在写模式
public:
// 初始化
int sht_rwlock_init()
{
pthread_mutex_init(&m1, nullptr);
pthread_mutex_init(&m2, nullptr);
return 0;
}
int sht_rwlock_destroy()
{
pthread_mutex_destroy(&m1);
pthread_mutex_destroy(&m2);
}
int sht_rwlock_rdlock()
{
pthread_mutex_lock(&m1);
read_num++;
if (read_num == 1)
pthread_mutex_lock(&m2);
// 改变成读者状态必须一定要放在申请到读者锁之后!!!!!!!!
cur_state = 1;
// 读者之间不是互斥关系,所以要解锁,否则其他读者进不来
pthread_mutex_unlock(&m1);
return 0;
}
int sht_rwlock_tryrdlock()
{
int ret1 = pthread_mutex_trylock(&m1);
if (ret1 != 0)
return -1;
if (read_num == 1)
{
int ret2 = pthread_mutex_trylock(&m2);
if (ret2 != 0)
return -1;
}
read_num++;
cur_state = 1;
pthread_mutex_unlock(&m1);
return 0;
}
int sht_rwlock_wrlock()
{
int ret = pthread_mutex_lock(&m2);
// 如果能走到这一步代表:读者申请到锁了,而读者和所有人互斥,所以我们改变状态并不归还锁
cur_state = -1;
return ret;
}
int sht_rwlock_trywrlock()
{
int ret = pthread_mutex_lock(&m2);
if (ret != 0)
return -1;
// 如果能走到这一步代表:读者申请到锁了,而读者和所有人互斥,所以我们改变状态并不归还锁
cur_state = -1;
return ret;
}
int sht_rwlock_unlock()
{
if (cur_state == -1)
{
// 写者还锁
pthread_mutex_unlock(&m2);
cur_state = 0;
}
else if (cur_state == 1)
{
// 读者还锁
pthread_mutex_lock(&m1);
if (read_num == 1)
pthread_mutex_unlock(&m2);
read_num--;
pthread_mutex_unlock(&m1);
}
else
return -1;
return 0;
}
};
} // namespace sht
思路2——两个条件变量一个锁(写者优先)
pthread_cond_t reader_cond; //读者队列
pthread_cond_t writer_cond; //写者队列
pthread_mutex_t m;
int reader_num; // 读者等待的数量
int writer_num; // 写者等待的数量
int cur_state;
// 1 : 代表读者模式
// 0 : 代表锁是为获取的
// -1 :代表写者模式
- 申请读锁的思路:
- 申请写锁的思路:
- 释放锁的思路
模拟实现
代码仓库 version2
#include <pthread.h>
// 写者优先的锁
namespace sht
{
class sht_rwlock
{
public:
int sht_rwlock_init()
{
pthread_cond_init(&reader_cond, nullptr);
pthread_cond_init(&writer_cond, nullptr);
pthread_mutex_init(&m, nullptr);
}
int sht_rwlock_destroy()
{
pthread_cond_destroy(&reader_cond);
pthread_cond_destroy(&writer_cond);
pthread_mutex_destroy(&m);
}
int sht_rwlock_rdlock()
{
pthread_mutex_lock(&m);
if (cur_state == 1)
{
// 当前在读模式,因为我们是写者优先所以我们看看是否有写者线程在等待
// 如果有我们就先执行写者,让读者去排队
// 如果没有,那么读锁就申请成功
while (writer_num > 0)
{
reader_num++;
pthread_cond_wait(&reader_cond, &m);
reader_num--;
}
}
else if (cur_state == -1)
{
// 如果当前是写者模式,读者直接去排队
reader_num++;
pthread_cond_wait(&reader_cond, &m);
reader_num--;
}
else
{
cur_state = 1;
}
pthread_mutex_unlock(&m);
}
int sht_rwlock_tryrdlock()
{
pthread_mutex_lock(&m);
if (cur_state == 0 || cur_state == 1 && writer_num == 0)
{
pthread_mutex_unlock(&m);
return 0;
}
return -1;
}
int sht_rwlock_wrlock()
{
pthread_mutex_lock(&m);
if (cur_state == -1)
{
writer_num++;
pthread_cond_wait(&writer_cond, &m);
writer_num--;
}
else if (cur_state == 1)
{
writer_num++;
pthread_cond_wait(&writer_cond, &m);
writer_num--;
}
else
{
// 把状态改成写者模式
cur_state = -1;
}
pthread_mutex_unlock(&m);
return 1;
}
int sht_rwlock_trywrlock()
{
pthread_mutex_lock(&m);
if (cur_state == -1)
{
pthread_mutex_unlock(&m);
return 0;
}
return -1;
}
int sht_rwlock_unlock()
{
pthread_mutex_init(&m, nullptr);
if (cur_state == 1)
{
if (writer_num > 0)
{
cur_state = -1;
pthread_cond_signal(&writer_cond);
}
else
{
if (reader_num > 0)
{
pthread_cond_signal(&reader_cond);
}
else
cur_state = 0; // 这一步一定不能丢,如果当前两个队列均为空时,要置为初始状态
// 不然线程就会卡住
}
}
else if (cur_state == -1)
{
// 写者来还锁,如果写队列不为空 ,唤醒写队列中的写者(写者优先)
// 如果写者队列空了,但是读者队列不为空,唤醒读者
// 两个队列都为空,把状态置成初始状态
if (writer_num > 0)
{
pthread_cond_signal(&writer_cond);
}
else
{
if (reader_num > 0)
{
cur_state = 1;
pthread_cond_signal(&reader_cond);
}
else
cur_state = 0;
}
}
else
{
return -1;
}
pthread_mutex_unlock(&m);
}
private:
pthread_cond_t reader_cond;
pthread_cond_t writer_cond;
pthread_mutex_t m;
int reader_num; // 读者等待的数量
int writer_num; // 写者等待的数量
int cur_state;
// 1 : 代表读者模式
// 0 : 代表锁是为获取的
// -1 :代表写者模式
};
} // namespace sht