读写锁
读写锁在加锁时,要区分是为了读而加锁还是为了写而加锁,所以和递归锁不同的是,它无法兼容Locker接口了。不过为了做到不依赖于特定平台,我
们可以利用Locker的接口来抽象锁的实现。利用现有的锁来实现读写锁。读写锁的可变的部分已经被Locker隔离了,所以读写锁本身不需要做成接口。
它只是一个普通对象而已:
struct _RwLocker;
typedef struct _RwLocker RwLocker;
RwLocker* rw_locker_create(Locker* rw_locker, Locker* rd_locker);
Ret rw_locker_wrlock(RwLocker* thiz);
Ret rw_locker_rdlock(RwLocker* thiz);
Ret rw_locker_unlock(RwLocker* thiz);
void rw_locker_destroy(RwLocker* thiz);
o 创建读写锁
RwLocker* rw_locker_create(Locker* rw_locker, Locker* rd_locker)
{
RwLocker* thiz = NULL;
return_val_if_fail(rw_locker != NULL && rd_locker != NULL, NULL);
thiz = (RwLocker*)malloc(sizeof(RwLocker));
if(thiz != NULL)
{
thiz->readers = 0;
thiz->mode = RW_LOCKER_NONE;
thiz->rw_locker = rw_locker;
thiz->rd_locker = rd_locker;
}
return thiz;
}
读写锁的基本要求是:写的时候不允许任何其它线程读或者写,读的时候允许其它线程读,但不允许其它线程写。所以在实现时,写的时候一定要加锁,第一
个读的线程要加锁,后面其它线程读时,只是增加锁的引用计数。我们需要两个锁:一个锁用来保存被保护的对象,一个锁用来保护引用计数。
o 加写锁
Ret rw_locker_wrlock(RwLocker* thiz)
{
Ret ret = RET_OK;
return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);
if((ret = locker_lock(thiz->rw_locker)) == RET_OK)
{
thiz->mode = RW_LOCKER_WR;
}
return ret;
}
加写锁很简单,直接加保护受保护对象的锁,然后修改锁的状态为已加写锁。后面其它的线程想写,就会这个锁上等待,如果想读也要等待(见后面)。
o 加读锁
Ret rw_locker_rdlock(RwLocker* thiz)
{
Ret ret = RET_OK;
return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);
if((ret = locker_lock(thiz->rd_locker)) == RET_OK)
{
thiz->readers++;
if(thiz->readers == 1)
{
ret = locker_lock(thiz->rw_locker);
thiz->mode = RW_LOCKER_RD;
}
locker_unlock(thiz->rd_locker);
}
return ret;
}
先尝试加保护引用计数的锁,增加引用计数。如果当前线程是第一个读,就要去加保护受保护对象的锁。如果此时已经有线程在写,就等待直到加锁成功,然后把锁的状态设置为已加读锁,最后解开保护引用计数的锁。
o 解锁
Ret rw_locker_unlock(RwLocker* thiz)
{
Ret ret = RET_OK;
return_val_if_fail(thiz != NULL, RET_INVALID_PARAMS);
if(thiz->mode == RW_LOCKER_WR)
{
thiz->mode == RW_LOCKER_NONE;
ret = locker_unlock(thiz->rw_locker);
}
else
{
assert(thiz->mode == RW_LOCKER_RD);
if((ret = locker_lock(thiz->rd_locker)) == RET_OK)
{
thiz->readers--;
if(thiz->readers == 0)
{
thiz->mode == RW_LOCKER_NONE;
ret = locker_unlock(thiz->rw_locker);
}
locker_unlock(thiz->rd_locker);
}
}
return ret;
}
解锁时根据状态来决定,解写读直接解保护受保护对象的锁。解读锁时,先要加锁保护引用计数的锁,引用计数减一。如果自己是最后一个读,才解保护受保护对象的锁,最后解开保护引用计数的锁。
从上面读写锁的实现,我们可以看出,读写锁要充分发挥作用,就要基于两个假设:
o 读写的不对称性,读的次数远远大于写的次数。像数据库就是这样,决大部分时间是在查询,而修改的情况相对少得多,所以数据库通常使用读写锁。
o 处于临界区的时间比较长。从上面的实现来看,读写锁实际上比正常加/解锁的次数反而要多,如果处于临界区的时间比较短,比如和修改引用计数差不多,使用读写锁,即使全部是读,它的效率也会低于正常锁。
本节示例请到这里下载。
分享到:
相关推荐
积分管理系统java源码 ...汇总java生态圈常用技术框架、开源中间件,系统架构、数据库、大公司架构案例、常用三方类库、项目管理、线上问题排查、个人成长、思考等知识 TheAlgorithms Github地址: 算法与数据结构
本项目大部分内容来自我近几年的收集和整理,根据计划将主要包含:Java相关知识、网络编程、并发编程、微服务、Linux、数据库、数据结构、算法等知识内容。本人对有深度的系列文章情有独钟,所以整理过程中会更加...
java8 集合源码分析 JavaBooks ...12.程序员成长 高效程序员的45个习惯 99.其他 详细推荐书单 基础入门 Java编程思想(Thinking in Java)、Java核心技术(Core Java) ,Java8 实战(Java in action),Eff
如果我们通过努力成功进入到了心仪的公司,一定不要懈怠放松,职场成长和新技术学习一样,不进则退。 在工作中发现我身边的人真的就是实力越强的越努力,最高级的自律,享受孤独。 整理收录一份Java核心知识图谱 ...
Java快速成长学习路线 @学习路线根据黑马程序员学习路线改编 Part1: Java基础&Web基础 Java基础 面向对象思想 集合框架 IO流 多线程与并发 异常处理 网络编程 数据库 MySQL Oracle JDBC C3P0 Druid 前端技术 HTML ...
生态圈常用技术框架、开源中间件,系统架构、数据库、大公司架构案例、常用三方类库、项目管理、线上问题排查、个人成长、思考等知识 :该项目是一系列小而专注的教程 - 每个教程都涵盖 Java 生态系统中单一且定义...
伴随着Java的成长,《Java核心技术》从第1版到第11版一路走来,得到了广大Java程序设计人员的青睐,成为一本畅销不衰的Java经典图书。 本书经全面修订,以涵盖Java 17的新特性。新版延续之前版本的优良传统,用数百...
Java开发者的高级知识,涵盖高并发、分布式系统、高可用、微服务、海量数据处理等。 2 很棒的编程书籍列表。 3 Java 虚拟机基本原理的总结。 4 任何编程语言的 LeetCode 解决方案。 5 程序员面试问题合集。 主要内容...
Java开发者的高级知识,涵盖高并发、分布式系统、高可用、微服务、海量数据处理等。 2 很棒的编程书籍列表。 3 Java 虚拟机基本原理的总结。 4 任何编程语言的 LeetCode 解决方案。 5 程序员面试问题合集。 主要内容...
职场智慧之程序员的职业规划 523 本章小结 524 第15章 大型网站的性能优化与安全 525 15.1 高效C#编码优化 526 15.2 页面(HTML)优化的方法 534 15.3 ASP.NET开发性能优化 540 15.3.1 如没必要,尽量使用静态HTML...
职场智慧之程序员的职业规划 523 本章小结 524 第15章 大型网站的性能优化与安全 525 15.1 高效C#编码优化 526 15.2 页面(HTML)优化的方法 534 15.3 ASP.NET开发性能优化 540 15.3.1 如没必要,尽量使用静态HTML...
然而,面对庞大的用户群体和高并发的购票请求,传统的购票系统可能会遇到一系列的挑战,例如系统崩溃、购票失败等问题。 为了解决这些问题,一款名为py12306的高效分布式多账号、多任务购票系统应运而生。 本文讲...