動手實現讀寫鎖


排他鎖的弊端
     在多個線程之間共享數據,普遍做法是加鎖讀寫,也就是同一個時刻只有一個線程能夠讀或者寫,以保證數據一致性,即線程安全。例如下面的偽代碼是常見的做法
 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

 

PS:本文為博主的原創文章,請尊重博主辛苦碼字的成果,轉載請注明鏈接
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM