码迷,mamicode.com
首页 > 其他好文 > 详细

动手实现读写锁

时间:2015-07-06 01:20:12      阅读:242      评论:0      收藏:0      [点我收藏+]

标签:

排他锁的弊端
     在多个线程之间共享数据,普遍做法是加锁读写,也就是同一个时刻只有一个线程能够读或者写,以保证数据一致性,即线程安全。例如下面的代码是普遍的做法
 1 void Read()
 2 {
 3     Lock(mutex);
 4 
 5     // 读取数据
 6 
 7     UnLock(mutex);
 8 }
 9 
10 void Write()
11 {
12     Lock(mutex);
13 
14     // 写入数据
15 
16     UnLock(mutex);
17 }

读写锁的设计

     这样的锁是具有排他性的,会在一定程度上影响程序的效率。假设有多个线程竞争获得读写权利,显然同一个时刻只有一个线程能够获得,要么进行读操作,要么进行写操作,而且,在读操作和读操作之间,或在写操作和写操作之间,同样具有排他性,存在下面的关系
1、读-写,排他
2、写-写,排他
3、读-读,排他
     既然排他锁影响程序效率,那么该如何优化呢?排他锁的目的,为了避免出现这样的竞争条件,在一个线程在未完成读操作之前,另一个线程写操作改变了数据,或者多个线程同时进行写操作。显然,在读操作和写操作之间,或在写操作和写操作之间,要求具有排他性,而排他锁带入到负面影响是,在读操作和读操作之间也具有了排他性。到这里,可以设想一下读写锁能够解决什么问题,没错,就是为了屏蔽这个负面影响,能够满足下面的关系
1、读-写,排他
2、写-写,排他
3、读-读,共享
     简单地说,就是可以有多个线程对读取数据。思路如下(伪代码)
 1 void Read()
 2 {
 3     Wait(condition);
 4 
 5     Lock(mutex);
 6     if(0 == Count++)
 7         Lock(semaphore);
 8     UnLock(mutex);
 9 
10     // 读取数据
11 
12     Lock(mutex);
13     if(0 == --Count)
14         UnLock(semaphore);
15     UnLock(mutex);
16 }
17 
18 void Write()
19 {
20     Destroy(condition);
21     Lock(semaphore);
22 
23     // 写入数据
24 
25     UnLock(semaphore);
26     Create(condition);
27 }

  在代码中,信号量semaphore的初值为1,为了读-写能够排他和写-写排他这两个关系。Count记录有多少个读操作,而mutex是为了保证Count线程安全。忽略condition相关的代码,可以说,已经实现了前面提到的读写锁。但为什么加入condition呢?原因是这样的,如果有多个线程在连续进行读操作,可能导致长时间不能进行写操作,也就是通常说的写锁饥饿。condition可以解决这个问题,如果有线程正在等待写操作,那么新的读操作先等待,等到写操作完成后再开始。这样可以保证获得读写操作的机会是相对公平的。

 
在Windows中实现读写锁
     在Windows平台,Vista和Server 2008及其更高的版本才开始提供读写锁相关的API,如果需要支持XP系统,那么往往需要自己实现读写锁机制。前面已经介绍过如何实现读写锁,这里不费口舌,直接贴出代码
技术分享
 1 // 实现代码
 2 
 3 class RWLock
 4 {
 5 public:
 6     RWLock();
 7     ~RWLock();
 8 public:
 9     void AcquireReadLock();
10     void ReleaseReadLock();
11     void AcquireWriteLock();
12     void ReleaseWriteLock();
13 private:
14     volatile DWORD      m_cnt;
15     CRITICAL_SECTION    m_cs;
16     HANDLE              m_evt;
17     HANDLE              m_sem;
18 };
19 
20 RWLock::RWLock()
21     : m_cnt(0)
22     , m_evt(NULL)
23     , m_cs(NULL)
24     , m_sem(NULL)
25 {
26     // 提倡的做法,在专门的初始化函数里创建和初始化这些变量
27 
28     ::InitializeCriticalSection(&m_cs);
29 
30     // Event初始处于激活状态,且必须是手动重置,否则存在死锁隐患,即等待Event前,先被激活了
31     m_evt = ::CreateEvent(NULL, TRUE, TRUE, NULL);
32     m_sem = ::CreateSemaphore(NULL, 1, 1, NULL);
33 }
34 
35 RWLock::~RWLock()
36 {
37     ::CloseHandle(m_sem);
38     ::CloseHandle(m_evt);
39     ::DeleteCriticalSection(&m_cs);
40 }
41 
42 void RWLock::AcquireReadLock()
43 {
44     ::WaitForSingleObject(m_evt, INFINITE);
45 
46     ::EnterCriticalSection(&m_cs);
47     if(0 == m_cnt++)
48         ::WaitForSingleObject(m_sem, INFINITE);
49     ::LeaveCriticalSection(&m_cs);
50 }
51 
52 void RWLock::ReleaseReadLock()
53 {
54     ::EnterCriticalSection(&m_cs);
55     if(0 == --m_cnt)
56         ::ReleaseSemaphore(m_sem, 1, NULL);
57     ::LeaveCriticalSection(&m_cs);
58 }
59 
60 void RWLock::AcquireWriteLock()
61 {
62     ::ResetEvent(m_evt);
63     ::WaitForSingleObject(m_sem, INFINITE);
64 }
65 
66 void RWLock::ReleaseWriteLock()
67 {
68     ::ReleaseSemaphore(m_sem, 1, NULL);
69     ::SetEvent(m_evt);
70 }
71 
72 // 使用示例
73 
74 void Read()
75 {
76     // 支持多个线程读取数据
77 
78     rwLock.AcquireReadLock();
79 
80     // 读取数据
81     
82     rwLock.ReleaseReadLock();
83 }
84 
85 void Write()
86 {
87     rwLock.AcquireWriteLock();
88 
89     // 写入数据
90 
91     rwLock.ReleaseWriteLock();
92 }
View Code

 

版权声明:本文为博主原创文章,请尊重博主辛苦码字的成果,转载请注明链接
http://www.cnblogs.com/coldberry/p/ReadWriteLock.html
 
 

动手实现读写锁

标签:

原文地址:http://www.cnblogs.com/coldberry/p/ReadWriteLock.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!