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. 转载---jboss简单使用

    初学Jboss,对于Jboss的基础认识以及配置做一些记录 Jboss基础: JBoss是什么–基于J2EE的应用服务器–开放源代码–JBoss核心服务不包括支持servlet/JSP的WEB容器,一 ...

  2. 自我总结(六)---(学习j2ee+j2ee第一阶段项目)

    自我完善的过程就是在不断的自我总结不断的改进. 学习了Struts2 Spring Hibernate. 十天前结束了这个课程.也考试了.这次考试老师说机试考的还不错.其实就是一个简单的用户登录,进行 ...

  3. Sqlserver_时间用法

    函数 描述 GETDATE() 返回当前的日期和时间 DATEPART() 返回日期/时间的单独部分 DATEADD() 在日期中添加或减去指定的时间间隔 DATEDIFF() 返回两个日期之间的时间 ...

  4. cocoapods ,错误大全

    出现这种警告 Your Podfile has had smart quotes sanitised. To avoid issues in the future, you should not us ...

  5. grub4dos通用菜单及相关工具包

    grub4dos通用菜单及相关工具包 全套工具包(含PE.ISO,可根据需要替换删减):http://pan.baidu.com/s/1i4EjWod模板文件3.3M(不含PE.ISO):http:/ ...

  6. EF Core » 关系

    对初学者理解关系很有用,先留下来,有时间边看边翻译. Caution 注意 This documentation is for EF Core. For EF6.x and earlier relea ...

  7. html5中插入视频和音频

    <audio src="1.mp3" controls></audio> <video src="1.mp4" controls& ...

  8. 5.6 WebDriver API实例讲解(16-30)

    16.操作单选框 被测试的网页为Demo1. Java语言版本的API实例代码: public static void operateRadio(){ driver.get("file:// ...

  9. mvc+ef中比较数据

    例如:根据Para表中的type和paraid 字段进行比较 public class TypeComparer : IEqualityComparer<Para> { bool IEqu ...

  10. c# 中的委托以及匿名方法lambda

    1.委托的定义internal delegate int MyAddFunDe(int a,int b)2.匿名方法1)MyAddFunDe fun = delegate(int a,int b){  ...