排他鎖的弊端
在多個線程之間共享數據,普遍做法是加鎖讀寫,也就是同一個時刻只有一個線程能夠讀或者寫,以保證數據一致性,即線程安全。例如下面的偽代碼是常見的做法
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 }
PS:本文為博主的原創文章,請尊重博主辛苦碼字的成果,轉載請注明鏈接