Windows平台下的读写锁
简单介绍Windows平台下的读写锁以及实现.

背景介绍
Windows在Vista 和 Server2008以后才开始提供读写锁API,即SRW系列函数(InitializeSRWLock, AcquireSRWLockShared, AcquireSRWLockExclusive等).考虑到目前Windows XP的装机量,只能自己实现一个读写锁了.

读写锁的目的和要求
读写锁的最基本目的是读锁可以共享,写锁必须独占.另外,我认为还有两点需要特别考虑:
1. 如果有写锁请求在等待,则新的读锁请求也应该等待.否则程序可能永远也没有机会获得写锁.
2. 一旦写锁被释放,所有在等待中的读锁和写锁请求应该可以公平竞争,而不管请求的先后,只要之前已经在等待就应该有获得锁的机会.
如果说第一点还可以再斟酌一下的话,第二点应该是必须要保证的.

读写锁的实现
总体思路比较简单:读写锁内部维护一些状态值,用临界端 CRITICAL_SECTION保护这些状态值访问的原子性和事件对象配合实现等待.

进入锁的情况:
1. 如果当前锁状态为空闲,则不管写锁还是读锁请求都允许进入,并设置相应的状态值.

2. 如果当前锁状态是读,新的写锁请求需要等待事件通知,并把写锁等待计数加一.
3. 如果当前锁状态是读,新的读锁请求:
3.1 如果没有写锁请求在等待,则允许读锁进入,并把读锁计数加一.
3.2 如果有写锁请求正在等待,则等待事件通知,并读锁等待计数加一(这样做的目的如上文所述,要使写锁有机会进入).

4. 如果当前锁状态为写,则不管读锁请求还是写锁请求,都等待事件通知并分别给读锁等待计数或者写锁等待计数加一.

解锁的情况:
如果锁释放时,读锁等待计数或者写锁等待计数不为0,则触发事件对象.

我使用手动事件对象,这样的话一旦锁被释放,所有正在等待的锁请求都将被激活,然后重新以竞争临界段的方式竞争锁进入权以保证公平性.不管等待请求时的先后,只要是锁释放前进入等待状态则锁一旦释放获得进入权的机会是均等的.

后记
我在实现读写锁之前,用Google搜索过,找到的几种Windows读写锁实现都不甚理想,主要问题如下:
1. 等待事件对象时占有了互斥量,这是一个非常不好的设计,这样的话其他锁请求将被阻塞在临界段外部,只有一个锁请求阻塞在Wait函数,对其他锁请求是不公平的.
2. 没考虑我开始提的目的2,即写锁可能永远都没机会进入.我不知道这个算不算缺点,但是自少在我的应用中,这种情况是不允许出现的,所以我重新设计过一个读写锁.

实现过程中一个让我很纠结的问题是:到底用自动事件还是手动事件. 用手动事件的好处是一旦触发,所有等待中的请求都被激活然后重新竞争,逻辑简单明了.缺点是可能会有很多冗余操作,比如有若干写锁还若干读锁请求正在等待进入,一旦锁释放,虽然全部请求(线程)都被激活,但是肯定只有一个请求能够进入,竞争失败的请求测试一下条件后继续挂起.如果使用自动事件,只有一个锁请求线程会被唤醒(Wait函数的特点,被唤醒的那个线程等同于已经竞争成功),似乎效率更高一些.获得进入权的锁请求再根据等待的情况决定是否继续触发事件对象:如果还有读请求和写请求在等待,则不触发;如果只有读请求在等待,则再触发一次以使其他读请求可以进入.考虑再三,我还是决定采用手动事件,毕竟在读锁的数量远大于写锁数量的情况下(这也是读写锁比较常见的场景)速度更快一些(不需要等待多次事件).

附录1 - C++代码

    1. #define RWLOCK_IDLE 0 /* 空闲 */
    2. #define RWLOCK_R 0x01 /* 读锁 */
    3. #define RWLOCK_W 0x02 /* 写锁 */
    4. class RWLock
    5. {
    6. private:
    7. int _st; /* 锁状态值 */
    8. int _rlockCount; /* 读锁计数 */
    9. int _rwaitingCount; /* 读等待计数 */
    10. int _wwaitingCount; /* 写等待计数 */
    11. HANDLE _ev; /* 通知事件 Event */
    12. //HANDLE _stLock; /* 访问状态值互斥量 */ /* 如果需要等待超时,则用 Mutex */
    13. CRITICAL_SECTION _stLock;
    14. public:
    15. RWLock(void);
    16. ~RWLock(void);
    17. void rlock();
    18. void wlock();
    19. void unlock();
    20. };
    21. RWLock::RWLock(void)
    22. : _rlockCount(0),
    23. _st(RWLOCK_IDLE),
    24. _rwaitingCount(0),
    25. _wwaitingCount(0)
    26. {
    27. //_stLock = CreateMutex(NULL, FALSE, NULL);
    28. //assert(_stLock != INVALID_HANDLE_VALUE);
    29. InitializeCriticalSection(&_stLock);
    30. /*
    31. * 假设当前有多个读锁请求正在等待写锁释放,那么当写锁被释放时,所有这些读锁都应该有机会获得执行.
    32. */
    33. _ev = CreateEvent(NULL, TRUE, FALSE, NULL);
    34. assert(_ev != INVALID_HANDLE_VALUE);
    35. }
    36. RWLock::~RWLock(void)
    37. {
    38. //CloseHandle(_stLock);
    39. DeleteCriticalSection(&_stLock);
    40. CloseHandle(_ev);
    41. }
    42. void RWLock::rlock()
    43. {
    44. bool isWaitReturn = false;
    45. while(1)
    46. {
    47. //WaitForSingleObject(_stLock, INFINITE);
    48. EnterCriticalSection(&_stLock);
    49. if(isWaitReturn)
    50. {
    51. /*
    52. * 等待事件返回,重新竞争锁.
    53. */
    54. --_rwaitingCount;
    55. }
    56. if(_st == RWLOCK_IDLE)
    57. {
    58. /*
    59. * 空闲状态,直接得到控制权
    60. */
    61. _st = RWLOCK_R;
    62. _rlockCount++;
    63. //ReleaseMutex(_stLock);
    64. LeaveCriticalSection(&_stLock);
    65. break;
    66. }
    67. else if( _st == RWLOCK_R)
    68. {
    69. if(_wwaitingCount > 0)
    70. {
    71. /*
    72. * 有写锁正在等待,则一起等待,以使写锁能获得竞争机会.
    73. */
    74. ++_rwaitingCount;
    75. ResetEvent(_ev);
    76. //SignalObjectAndWait(_stLock, _ev, INFINITE, FALSE);
    77. LeaveCriticalSection(&_stLock);
    78. /*
    79. * 虽然 LeaveCriticalSection() 和 WaitForSingleObject() 之间有一个时间窗口,
    80. * 但是由于windows平台的事件信号是不会丢失的,所以没有问题.
    81. */
    82. WaitForSingleObject(_ev, INFINITE);
    83. /*
    84. * 等待返回,继续尝试加锁.
    85. */
    86. isWaitReturn = true;
    87. }
    88. else
    89. {
    90. /*
    91. * 得到读锁,计数+1
    92. */
    93. ++_rlockCount;
    94. //ReleaseMutex(_stLock);
    95. LeaveCriticalSection(&_stLock);
    96. break;
    97. }
    98. }
    99. else if(_st == RWLOCK_W)
    100. {
    101. /*
    102. * 等待写锁释放
    103. */
    104. ++_rwaitingCount;
    105. ResetEvent(_ev);
    106. //SignalObjectAndWait(_stLock, _ev, INFINITE, FALSE);
    107. LeaveCriticalSection(&_stLock);
    108. WaitForSingleObject(_ev, INFINITE);
    109. /*
    110. * 等待返回,继续尝试加锁.
    111. */
    112. isWaitReturn = true;
    113. }
    114. else
    115. {
    116. assert(0);
    117. break;
    118. }
    119. }
    120. }
    121. void RWLock::wlock()
    122. {
    123. bool isWaitReturn = false;
    124. while(1)
    125. {
    126. //WaitForSingleObject(_stLock, INFINITE);
    127. EnterCriticalSection(&_stLock);
    128. if(isWaitReturn) --_wwaitingCount;
    129. if(_st == RWLOCK_IDLE)
    130. {
    131. _st = RWLOCK_W;
    132. //ReleaseMutex(_stLock);
    133. LeaveCriticalSection(&_stLock);
    134. break;
    135. }
    136. else
    137. {
    138. ++_wwaitingCount;
    139. ResetEvent(_ev);
    140. //SignalObjectAndWait(_stLock, _ev, INFINITE, FALSE);
    141. LeaveCriticalSection(&_stLock);
    142. WaitForSingleObject(_ev, INFINITE);
    143. isWaitReturn = true;
    144. }
    145. }
    146. }
    147. void RWLock::unlock()
    148. {
    149. //WaitForSingleObject(_stLock, INFINITE);
    150. EnterCriticalSection(&_stLock);
    151. if(_rlockCount > 0)
    152. {
    153. /* 读锁解锁 */
    154. --_rlockCount;
    155. if( 0 == _rlockCount)
    156. {
    157. _st = RWLOCK_IDLE;
    158. /* 释放 */
    159. if( _wwaitingCount > 0 || _rwaitingCount > 0 )
    160. {
    161. /*
    162. * 此时有锁请求正在等待,激活所有等待的线程.(手动事件).
    163. * 使这些请求重新竞争锁.
    164. */
    165. SetEvent(_ev);
    166. }
    167. else
    168. {
    169. /* 空闲 */
    170. }
    171. }
    172. else
    173. {
    174. /* 还有读锁 */
    175. }
    176. }
    177. else
    178. {
    179. _st = RWLOCK_IDLE;
    180. /* 写锁解锁 */
    181. if( _wwaitingCount > 0 || _rwaitingCount > 0 )
    182. {
    183. /*
    184. * 如果在占有互斥量_stLock的情况下,触发事件,那么可能会使一些锁请求不能得到竞争机会.
    185. * 假设调用unlock时,另一个线程正好调用rlock或者wlock.如果不释放互斥量,只有之前已经等待的锁请求有机会获得锁控制权.
    186. */
    187. SetEvent(_ev);
    188. }
    189. else
    190. {
    191. /* 空闲 */
    192. }
    193. }
    194. //ReleaseMutex(_stLock);
    195. LeaveCriticalSection(&_stLock);
    196. }

Windows平台下的读写锁的更多相关文章

  1. 不同WINDOWS平台下磁盘逻辑扇区的直接读写

    不同WINDOWS平台下磁盘逻辑扇区的直接读写 关键字:VWIN32.中断.DeviceIoControl 一.概述 在DOS操作系统下,通过BIOS的INT13.DOS的INT25(绝对读).INT ...

  2. Windows平台下利用APM来做负载均衡方案 - 负载均衡(下)

    概述 我们在上一篇Windows平台分布式架构实践 - 负载均衡中讨论了Windows平台下通过NLB(Network Load Balancer) 来实现网站的负载均衡,并且通过压力测试演示了它的效 ...

  3. windows平台下基于VisualStudio的Clang安装和配置

    LLVM 是一个开源的编译器架构,它已经被成功应用到多个应用领域.Clang是 LLVM 的一个编译器前端,它目前支持 C, C++, Objective-C 以及 Objective-C++ 等编程 ...

  4. [转]Windows平台下安装Hadoop

    1.安装JDK1.6或更高版本 官网下载JDK,安装时注意,最好不要安装到带有空格的路径名下,例如:Programe Files,否则在配置Hadoop的配置文件时会找不到JDK(按相关说法,配置文件 ...

  5. MongoDB学习总结(一) —— Windows平台下安装

    > 基本概念 MongoDB是一个基于分布式文件存储的开源数据库系统,皆在为WEB应用提供可扩展的高性能数据存储解决方案.MongoDB将数据存储为一个文档,数据结构由键值key=>val ...

  6. windows平台下基于QT和OpenCV搭建图像处理平台

        在之前的博客中,已经分别比较详细地阐述了"windows平台下基于VS和OpenCV"以及"Linux平台下基于QT和OpenCV"搭建图像处理框架,并 ...

  7. Windows平台下的内存泄漏检测

    在C/C++中内存泄漏是一个不可避免的问题,很多新手甚至有许多老手也会犯这样的错误,下面说明一下在windows平台下如何检测内存泄漏. 在windows平台下内存泄漏检测的原理大致如下. 1. 在分 ...

  8. Spotlight on Mysql在Windows平台下的安装及使用简介

    Spotlight on Mysql在Windows平台下的安装及使用简介   by:授客 QQ:1033553122 1.   测试环境 Win7 64位 mysql-connector-odbc- ...

  9. 排错-windows平台下访问oracle em出现空白的解决方法

    排错-windows平台下访问oracle em出现空白的解决方法 by:授客 QQ:1033553122 问题描述 IE浏览器本地访问oem,出现空白页面,就左上角有一行字符 http://loca ...

随机推荐

  1. iframe页面调用父窗口JS函数

    A页面iframe 页面B, 此时 如果要在B页面调用父页面A的函数 B页面写法 parent.functionName(); 错误1: 解决办法 var js_domain_async = 'bai ...

  2. linux特殊字符

    linux特殊字符: * 匹配文件名中的任何字符串,包括空字符串. ? 匹配文件名中的任何单个字符. [...] 匹配[ ]中所包含的任何字符. [!...] 匹配[ ]中非感叹号!之后的字符. 当s ...

  3. 快速认识ELK中的L - Logstash

    快速认识ELK中的L - Logstash 原创 2016-12-07 杜亦舒 简介 Logstash 是一个开源的数据采集引擎. Logstash 就像是一个管子,左面接数据源接收数据,右面接存储目 ...

  4. 多语言的sitemap xml

    请注意一下 sitemap xml 也有多语言的

  5. 机器学习与R语言

    此书网上有英文电子版:Machine Learning with R - Second Edition [eBook].pdf(附带源码) 评价本书:入门级的好书,介绍了多种机器学习方法,全部用R相关 ...

  6. Machine Learning - 第4周(Neural Networks: Representation)

    Neural networks is a model inspired by how the brain works. It is widely used today in many applicat ...

  7. An unknown server-side error occurred while processing the command.处理

    在调用resetAPP()时,报错:An unknown server-side error occurred while processing the command. 怎么解决呢?请看: 额,Ap ...

  8. mongodb配置及简单示例

    安装 在官网下载安装 https://www.mongodb.com/ 配置 我的电脑—>右键属性—>左边列表中的高级程序设置—>环境变量 点击path 把你的mongodb文件路径 ...

  9. robotframework笔记16

    发布处理具有相同名称的关键字 使用机器人框架要么是关键词 图书馆 关键字 或 用户的关键字 . 前来自 标准 库 或 外部库 ,后者 中创建相同的文件在使用或进口 资源文件 . 许多关键字使用时,是很 ...

  10. hdu----(1599)最大子矩阵(几何/dp)

    最大子矩阵 Time Limit: 30000/10000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Sub ...