一步一步实现读写锁
多线程编程中,需要对共享变量进行加锁。但是频繁地加锁,会对程序效率有很大影响。在某些读多写少的场景下,多个线程进行读数据时,如果都加互斥锁,这显然是不必须的。于是读写锁便应运而生。
读写锁的加锁规则:
1 如果没有加写锁时,那么多个线程可以同时加读锁;如果有加写锁时,不可以加读锁
2 不管是加了读锁还是写锁,都不能继续加写锁。
满足这两个条件,便可以初步实现一个读写锁。我们用两个锁,一个变量,实现一个简单的读写锁,代码如下
class rwlock
{
public:
rwlock(): read_cnt(0)
{
pthread_mutex_init(&read_mtx,NULL);
pthread_mutex_init(&write_mtx,NULL);
}
~ rwlock()
{
pthread_mutex_destroy(&read_mtx);
pthread_mutex_destroy(&write_mtx);
}
void readLock()
{
pthread_mutex_lock(&read_mtx);
if (++read_cnt == 1)
pthread_mutex_lock(&write_mtx);
pthread_mutex_unlock(&read_mtx);
}
void readUnlock()
{
pthread_mutex_lock(&read_mtx);
if (--read_cnt == 0)
pthread_mutex_unlock(&write_mtx);
pthread_mutex_unlock(&read_mtx);
}
void writeLock()
{
pthread_mutex_lock(&write_mtx);
}
void writeUnlock()
{
pthread_mutex_unlock(&write_mtx);
}
private:
pthread_mutex_t read_mtx;
pthread_mutex_t write_mtx;
int read_cnt; // 读锁个数
};
首先,在加读锁时,判断读者数量,如果为1,说明自己是第一个读者,这时要加写锁。如果没有写者,加锁成功。如果有写者,那么需要等待写锁释放。
其次,加写锁时,就是直接锁write_mtx,如果没有其他任何读者或者写者,加锁成功;否则就等待write_mtx被释放。
这种实现方法简单明了,但是存在一个问题。当读写锁被读者占有时,这时来了写者需要等待读锁释放,如果又来了读锁却可以加锁成功。这样就可能导致,写锁很难获取,读锁一直无法释放。实际应用中,我们并不期望如此,因为这有可能导致数据不能及时更新,读取的数据是过期的。很明显,写锁的优先级应该高于读锁。那么如何实现这样的读写锁呢?
那么在读写锁的数据结构中,应该需要两个变量,来表示在等待的读者和写者的数量。首先给出读写锁的定义
class rwlock
{
public:
rwlock();
~rwlock();
void readlock();
void writelock();
void unlock();
int tryreadlock();
int trywritelock();
private:
pthread_mutex_t rwmutex;
int refcount; // -1表示有写者,0表示没有加锁,正数表示有多少个读者
int readwaiters;
int writewaiters;
pthread_cond_t readcond;
pthread_cond_t writecond;
};
实现如下:
1.构造函数,负责初始化变量
rwlock::rwlock()
{
refcount = 0;
readwaiters = 0;
writewaiters = 0;
pthread_mutex_init(&rwmutex,NULL);
pthread_cond_init(&readcond, NULL);
pthread_cond_init(&writecond, NULL);
}
2 析构函数,销毁资源
rwlock::~rwlock()
{
refcount = 0;
readwaiters = 0;
writewaiters = 0;
pthread_mutex_destroy(&rwmutex);
pthread_cond_destroy(&readcond);
pthread_cond_destroy(&writecond);
}
3 加读锁
void rwlock::readlock()
{
pthread_mutex_lock(&rwmutex);
while(refcount < 0)
{
readwaiters++;
pthread_cond_wait(&readcond,&rwmutex);
readwaiters--;
}
refcount++;
pthread_mutex_unlock(&rwmutex);
}
首先,对rwmutex加锁,主要是为了读区refcount变量。然后在while循环中,等待读信号量。这里要注意的是,while不能用if来判断。我们可能会认为,在readcond有信号时,说明写者已经释放了写锁,这时refcount必然会等于0,没必要用while循环。但是,请注意,pthread_cond_wait这个函数的执行过程。首先,它会释放锁rwmutex,然后等待readcond有信号,最后获得信号量时,再对rwmutex加锁。这样就会存在一种情况,在readcond获得信号之后,还没来得及对rwmutex进行加锁,另外一个线程这时来获取写锁,很显然它可以获取到,refcount变成了-1。如果不对refcount进行判断就会出错。
4 加写锁
void rwlock::writelock()
{
pthread_mutex_lock(&rwmutex);
while(refcount != 0)
{
writewaiters++;
pthread_cond_wait(&writecond,&rwmutex);
writewaiters--;
}
refcount = -1;
pthread_mutex_unlock(&rwmutex);
}
注意点和读锁一样,都是要while循环,不再重复
5释放锁
void rwlock::unlock()
{
pthread_mutex_lock(&rwmutex);
if(refcount == -1)
refcount = 0;
else
refcount--;
if(refcount == 0)
{
if(writewaiters > 0)
pthread_cond_signal(&writecond);
else if(readwaiters > 0)
pthread_cond_broadcast(&readcond);
}
pthread_mutex_unlock(&rwmutex);
}
解锁时,如果recount==0,说明已经没有任何人再使用读写锁,那么首先判断是否有写锁等待,如果是,置writecond有信号。如果没有写者,只有读者,那么对readcond信号量进行广播。
到这里,读写锁的功能就介绍完了。但是注意上面的加锁接口都是阻塞的,我们接着介绍非阻塞的加锁接口
6 非阻塞读锁
int rwlock::tryreadlock()
{
int ret = 0;
pthread_mutex_lock(&rwmutex);
if(refcount < 0 || writewaiters > 0)
{
ret = -1;
}
else
refcount++;
pthread_mutex_unlock(&rwmutex);
return ret;
}
7 非阻塞写锁
int rwlock::trywritelock()
{
int ret = 0;
pthread_mutex_lock(&rwmutex);
if(refcount != 0 )
{
ret = -1;
}
else
refcount = -1;
pthread_mutex_unlock(&rwmutex);
return ret;
}
非阻塞接口相对比阻塞接口简单,这里就不再重复讲述了。
总结:本文详细介绍了读写锁的功能,以及实现方法。实现都是基于posix接口,适用于所有类unix系统。至于windows系统,可参考这篇博文
http://blog.csdn.net/querw/article/details/7214925
- 【腾讯云的1001种玩法】十分钟搞定云架构 · 什么是Bucket、什么是Object
- 【腾讯云的1001种玩法】十分钟轻松搞定云架构 · 负载均衡的最佳实践
- 【黑客浅析】像黑客一样思考
- 【腾讯云的1001种玩法】 十分钟轻松搞定云架构 · 负载均衡的几种均衡模式
- ASP.NET Web API的Controller是如何被创建的?
- 【腾讯云的1001种玩法】十分钟轻松搞定云架构:COS的两种上传模式
- 物流行业迎变革,云计算是基础,大数据是关键
- Socket学习总结系列(二) -- CocoaAsyncSocket
- 比特币勒索病毒肆虐,腾讯云安全专家给你支招
- HTML5 直播协议之 WebSocket 和 MSE
- IoC在ASP.NET Web API中的应用
- 跟鹅厂老司机学技术之一:“遇见” Kotlin
- 简单的 H5 视频推流解决方案
- 来腾讯云开发者实验室学习.NET
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- C# 的sql server like 的参数
- sql server 字符串替换函数REPLACE
- sql server 更新两个表的某个字段
- HTML 引用Css样式的四种方式
- Java也可以像python般range出连续集合
- c# dev控件 gridcontrol 数据跟随鼠标滚轮滚动也可以编辑
- Apollo(阿波罗)配置中心Java客户端使用指南使用指南
- DevExpress.LookUpEdit控件实现自动搜索定位功能 兼使用方法(looUpEdit可编辑)
- dev GridControl直接打印 纵向合并单元格
- Dooring可视化之从零实现动态表单设计器
- 我不是最后一个知道MDC的吧?
- 实战编写 wireshark 插件解析私有协议
- 安卓 APP 三代加壳方案的研究报告
- 将博客主题替换成 Clean Blog
- Go 数据存储篇(一):基于内存存储实现数据增删改查功能