上篇的互斥鎖 (Critical Section) 將所有存取序列化。但實際場景常為:多讀者可並發、讀寫/寫寫需互斥。例如快取、設定資料 — 讀取遠多於寫入。讀寫鎖能大幅提升並發度。
N 個 Mutex (N = 最大並發讀者數)。WaitForMultipleObjects(N, handles, FALSE, timeout) — 任一 Mutex 可用即成功 (獲取該 Mutex 所有權)。WaitForMultipleObjects(N, handles, TRUE, timeout) — 全部 Mutex 皆可用才成功 (獨佔所有)。ReleaseMutex。原理:讀者佔用 1 個 Mutex,最多 N 人並發;寫者需佔用所有 N 個,與任何讀者/寫者互斥。
_RWLockBase — 定義介面、支援多型
class _RWLockBase {
friend class RWLock;
protected:
virtual DWORD ReadLock(int timeout) = 0;
virtual void ReadUnlock(int handleIndex) = 0;
virtual DWORD WriteLock(int timeout) = 0;
virtual void WriteUnlock() = 0;
};
RWLockBase<maxReadCount> — 編譯期決定 Mutex 數量
template // 預設 3 讀者並發
class RWLockBase : public _RWLockBase {
HANDLE handles[maxReadCount]; // 陣列置於堆疊,無動態配置
// 實作四個虛函數...
DWORD ReadLock(int timeout) {
return ::WaitForMultipleObjects(maxReadCount, handles, FALSE, timeout);
}
void ReadUnlock(int idx) { ::ReleaseMutex(handles[idx]); }
DWORD WriteLock(int timeout) {
return ::WaitForMultipleObjects(maxReadCount, handles, TRUE, timeout);
}
void WriteUnlock() {
for (int i=0; i
模板優勢: 客戶端僅需 class MyData : public RWLockBase<> { }; 一次指定參數,無須重複傳遞數量,且陣列置於堆疊避免動態配置開銷。
RWLock — 管理鎖生命週期
class RWLock {
_RWLockBase* _pObj;
bool _readLock;
bool _success;
int _handleIndex; // 讀鎖時記錄取得的 Mutex 索引
public:
RWLock(_RWLockBase* pObj, bool readLock=true, int timeout=3000)
: _pObj(pObj), _readLock(readLock), _success(false), _handleIndex(-1) {
if (!_pObj) return;
if (_readLock) {
DWORD ret = _pObj->ReadLock(timeout);
if (ret < WAIT_ABANDONED) { _handleIndex = ret - WAIT_OBJECT_0; _success = true; }
} else {
DWORD ret = _pObj->WriteLock(timeout);
if (ret < WAIT_ABANDONED) _success = true;
}
}
~RWLock() {
if (!_pObj) return;
if (_handleIndex > -1) _pObj->ReadUnlock(_handleIndex);
else _pObj->WriteUnlock();
}
bool IsLockSuccess() const { return _success; }
};
class MyData : public RWLockBase<> { /* 資料成員 */ };
MyData data;
// 讀取路徑
void ReadData() {
RWLock lock(&data, true); // 讀鎖
if (lock.IsLockSuccess()) { /* 安全讀取 */ }
}
// 寫入路徑
void WriteData() {
RWLock lock(&data, false); // 寫鎖
if (lock.IsLockSuccess()) { /* 安全寫入 */ }
}
| 方案 | 優點 | 缺點 |
|---|---|---|
| 模板 + 虛函數 (本文) | 客戶端代碼極簡、陣列在堆疊、編譯期定參數 | 每實例額外 4/8 字節 vptr、虛函數呼叫間接開銷 |
| 非模板 + 動態陣列 | 無虛函數開銷 | 需動態配置陣列 (heap)、構造時延遲、指標額外空間、客戶端需兩次指定大小 |
實測差異極微,推薦模板版本 以易用性優先。
001 [15:07:28.484] read thread 0 started
002 [15:07:28.484] read thread 1 started
003 [15:07:28.484] read thread 2 started // 3 讀者並發
004 [15:07:29.484] read thread 0 ended
005 [15:07:29.484] read thread 3 started // 釋放一個槽位,第 4 讀者進入
006 [15:07:29.484] read thread 1 ended
007 [15:07:29.484] read thread 4 started // 第 5 讀者進入
008 [15:07:29.484] read thread 2 ended
009 [15:07:30.484] read thread 3 ended
010 [15:07:30.484] read thread 4 ended // 所有讀者結束
011 [15:07:30.484] write thread 0 started // 寫者取得獨佔
012 [15:07:31.078] write thread 0 ended
013 [15:07:31.078] write thread 1 started
014 [15:07:31.484] write thread 2 timeout // 等待超時 (3 秒)
015 [15:07:31.484] write thread 3 timeout
016 [15:07:31.484] write thread 4 timeout
017 [15:07:31.687] write thread 1 ended
完美展示:讀讀並發 (3)、讀寫互斥 (讀完才寫)、寫寫互斥 (序列化)、超時機制。
結合 WaitForMultipleObjects、Mutex 陣列、模板參數化、RAII 封裝,實作出高效能、易用、異常安全的讀寫鎖。此模式廣泛適用於高讀低寫的共享資料結構 (快取、索引、設定表等)。