讀寫鎖是並發編程中「讀者共享、寫者獨佔」的經典同步原語。Linux 核心(pthread)以 mutex + 兩個條件變數 實現,並採寫者優先策略防止寫者餓死。本文拆解 pthread_rwlock_t 結構與三大核心函數 rdlock、wrlock、unlock 的邏輯細節。
typedef struct {
pthread_mutex_t rw_mutex; // 保護整個結構體
pthread_cond_t rw_condreaders; // 通知等待讀者
pthread_cond_t rw_condwriters; // 通知等待寫者
int rw_waitreaders; // 等待讀鎖線程數
int rw_waitwriters; // 等待寫鎖線程數
int rw_refcount; // 鎖狀態:-1=寫鎖,0=空閒,>0=讀鎖計數
} pthread_rwlock_t;
pthread_rwlock_rdlock()int pthread_rwlock_rdlock(pthread_rwlock_t *rw) {
int result;
pthread_mutex_lock(&rw->rw_mutex);
// 寫者優先:有寫鎖被持有 OR 有寫者在等待 → 讀者必須等待
while (rw->rw_refcount < 0 || rw->rw_waitwriters > 0) {
rw->rw_waitreaders++;
result = pthread_cond_wait(&rw->rw_condreaders, &rw->rw_mutex);
rw->rw_waitreaders--;
if (result != 0) break;
}
if (result == 0) rw->rw_refcount++; // 成功獲取,讀者計數+1
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
關鍵點:rw_refcount < 0 表示寫鎖被持有;rw_waitwriters > 0 實現寫者優先——新來的讀者不得插隊,必須等所有等待寫者先獲得鎖並釋放。
pthread_rwlock_wrlock()int pthread_rwlock_wrlock(pthread_rwlock_t *rw) {
int result;
pthread_mutex_lock(&rw->rw_mutex);
// 僅檢查鎖是否空閒(refcount == 0),不在乎等待的讀者
while (rw->rw_refcount != 0) {
rw->rw_waitwriters++;
result = pthread_cond_wait(&rw->rw_condwriters, &rw->rw_mutex);
rw->rw_waitwriters--;
if (result != 0) break;
}
if (result == 0) rw->rw_refcount = -1; // 標記為寫鎖
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
關鍵點:寫者只關心「鎖是否完全空閒」,不理會等待中的讀者。配合讀端的 rw_waitwriters > 0 判斷,形成寫者優先語意。
pthread_rwlock_unlock()int pthread_rwlock_unlock(pthread_rwlock_t *rw) {
int result = 0;
pthread_mutex_lock(&rw->rw_mutex);
if (rw->rw_refcount > 0) rw->rw_refcount--; // 讀鎖遞減
else if (rw->rw_refcount == -1) rw->rw_refcount = 0; // 寫鎖歸零
else printf("invalid refcount=%d\n", rw->rw_refcount);
// 喚醒策略:寫者優先
if (rw->rw_waitwriters > 0) {
if (rw->rw_refcount == 0) // 僅在鎖完全空閒時喚醒寫者
result = pthread_cond_signal(&rw->rw_condwriters);
} else if (rw->rw_waitreaders > 0) {
result = pthread_cond_signal(&rw->rw_condreaders); // 無寫者等待才喚醒讀者
}
pthread_mutex_unlock(&rw->rw_mutex);
return result;
}
if (rw->rw_waitwriters > 0 && rw->rw_refcount == 0)?若這樣寫,當「有寫者等待」且「仍有讀者持有鎖(refcount > 0)」時,條件為假,會落入 else if 喚醒讀者 → 新讀者獲取鎖 → 寫者持續被餓死。
正確邏輯:只要有寫者等待,釋放鎖時絕不喚醒讀者,只在鎖完全空閒(refcount==0)時喚醒一個寫者;否則什麼都不做,等最後一個讀者釋放鎖時再喚醒寫者。
這四條不變量由 rw_refcount 正負號、rw_waitwriters 計數、條件變數精密協作保證。理解此實現,有助於排查死鎖、餓死、優先級反轉等並發疑難雜症。