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. 转:最小区间:k个有序的数组,找到最小区间使k个数组中每个数组至少有一个数在区间中

    转:http://www.itmian4.com/thread-6504-1-1.html 最小区间原题 k个有序的数组,找到最小的区间范围使得这k个数组中,每个数组至少有一个数字在这个区间范围内.比 ...

  2. C#_Winfrom下的中英文翻译

    Winform下的语言国际化,几行代码轻松实现   最近做了一些关于winform的项目,需要用到winform的语言国际化,在初使化的时候用起来非常方便.可以参考一下: 核心逻辑: 预览效果演示: ...

  3. servlet 文件上传简易版

    package cn.itcast.servlet; import java.io.File;import java.io.IOException;import java.util.List; imp ...

  4. jQuery Ajax学习

    地址:http://www.w3school.com.cn/jquery/jquery_ref_ajax.asp

  5. 编程获得CPU的主频

    CPU的主频,即CPU内核工作的时钟频率(CPU Clock Speed).CPU的主频表示在CPU内数字脉冲信号震荡的速度.主频和实际的运算速度存在一定的关系,但目前还没有一个确定的公式能够定量两者 ...

  6. noip2016酱油记day1

    真的是noip2016酱油记了. t1模拟,应该可以过. t2用了个简单的桶瞎搞,估计剩50pt了. t3直接不会写. 心好累... 考的分数肯定没去年高. 但不论如何,明天正常发挥就好. 正常发挥下 ...

  7. 浅谈线程池(上):线程池的作用及CLR线程池

    原文地址:http://blog.zhaojie.me/2009/07/thread-pool-1-the-goal-and-the-clr-thread-pool.html 线程池是一个重要的概念. ...

  8. css选择符有哪些?哪些属性可以继承?优先级算法如何计算?内联和important哪个优先

    通配选择符* { sRules }  类型选择符E { sRules }  td { font-size:14px; width:120px; }   属性选择符 E [ attr ] { sRule ...

  9. 修复iPhone的safari浏览器上submit按钮圆角bug

    今天在公司写了一个登录页面效果,让我碰到一个怪异的问题——"表单中的input type=submit和input type=reset按钮在iPhone的safari浏览器下圆角有一个bu ...

  10. [redis] 分布式 Redis 的 CRUD 实现

    1.applicationContext-redis.xml <?xml version="1.0" encoding="UTF-8"?> < ...