讀寫鎖適用於「大量讀、少量寫」場景。標準 pthread_rwlock_t 在高併發讀取下會導致寫入者飢餓(不滿足特性 3:寫等待時阻塞後續讀者)。本文分析缺陷,並使用 GCC/MSVC 內建原子操作,實作一個滿足三大特性、跨平台(Linux/Windows)的高效能讀寫鎖。
// 測試:10 個讀執行緒、2 個寫執行緒
// 結果:寫執行緒長時間無法取得鎖,甚至永久飢餓

// 初始化只需歸零
struct rwlock {
int rlock; // 讀者計數
int wlock; // 寫者旗標 (0/1)
};
// 使用:struct rwlock lock = {0, 0};
// 讀鎖:優先檢查 wlock,成功遞增 rlock,若期間 wlock 變 1 則回滾
static void rwlock_rlock(struct rwlock *lock) {
for (;;) {
while (lock->wlock) __sync_synchronize(); // 自旋等待寫釋放
__sync_add_and_fetch(&lock->rlock, 1); // 原子 +1
if (!lock->wlock) break; // 確認無寫者介入
__sync_add_and_fetch(&lock->rlock, -1); // 回滾
}
}
// 寫鎖:原子 CAS 佔用 wlock,再等待所有讀者離開
static void rwlock_wlock(struct rwlock *lock) {
while (__sync_lock_test_and_set(&lock->wlock, 1)) ; // 自旋搶佔
while (lock->rlock) __sync_synchronize(); // 等待讀者歸零
}
// 解鎖
static inline void rwlock_runlock(struct rwlock *lock) {
__sync_add_and_fetch(&lock->rlock, -1);
}
static inline void rwlock_wunlock(struct rwlock *lock) {
__sync_lock_release(&lock->wlock);
}
統一 GCC 與 MSVC (Windows) 原子操作介面:
#if defined(_MSC_VER) // Windows / Visual Studio
#include <Windows.h>
#define ATOM_ADD_FETCH(v,a) InterlockedAdd((LONG volatile*)&(v), (LONG)(a))
#define ATOM_SET(v,a) InterlockedExchange((LONG volatile*)&(v), (LONG)(a))
#define ATOM_LOCK(v) while(ATOM_SET(v,1)) Sleep(0)
#define ATOM_UNLOCK(v) ATOM_SET(v,0)
#define ATOM_SYNC() MemoryBarrier()
#elif defined(__GNUC__) // GCC / Clang
#define ATOM_ADD_FETCH(v,a) __sync_add_and_fetch(&(v), (a))
#define ATOM_SET(v,a) __sync_lock_test_and_set(&(v), (a))
#define ATOM_LOCK(v) while(ATOM_SET(v,1)) usleep(2)
#define ATOM_UNLOCK(v) __sync_lock_release(&(v))
#define ATOM_SYNC() __sync_synchronize()
#endif
// 測試同前:10 讀、2 寫
// 編譯:gcc -Wall -ggdb2 -I. -o test_atom_rwlock.out test_atom_rwlock.c -lpthread
// 執行:讀寫交錯正常,寫入不飢餓,所有執行緒正常退出

// 推薦風格:型別與 * 緊貼,變數名獨立
int * piyo; // 宣告
int * heoo(int a, int * pi); // 原型
int * heoo(int a, int b) { ... } // 定義
避免 int* piyo, *hoge; 易誤讀;亦避免 int *piyo; 與定義處風格不一致。統一風格提升可維護性。
💡 工程感悟: 標準庫並非完美,理解其局限並能自行實作核心同步原語,是系統程式設計師的基本功。原子操作 + 記憶體屏障 是無鎖/鎖實作的基石。