多線程編程之讀寫鎖
在《多線程編程之Linux環境下的多線程(二)》一文中提到了Linux環境下的多線程同步機制之一的讀寫鎖。本文再詳細寫一下讀寫鎖的概念和原理。
一、什麽是讀寫鎖
讀寫鎖(也叫共享-獨占鎖)實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高並發性,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數為實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。
如果讀寫鎖當前沒有讀者,也沒有寫者,那麽寫者可以立刻獲得讀寫鎖,否則它必須“自旋”在那裏,直到沒有任何寫者或讀者。如果讀寫鎖沒有寫者,那麽讀者可以立即獲得該讀寫鎖,否則讀者必須“自旋”在那裏,直到寫者釋放該讀寫鎖。讀寫鎖適合於對數據結構的讀次數比寫次數多很多的場合。
二、一種Linux環境下的實現方法
下面利用pthread.h提供的mutex和condition來實現一個讀寫鎖:
#include <pthread.h>
struct rwlock {
pthread_mutex_t lock;
pthread_cond_t read, write;
unsigned readers, writers, read_waiters, write_waiters;
};
void reader_lock(struct rwlock *self) {
pthread_mutex_lock(&self->lock);
if (self->writers || self->write_waiters) {
self->read_waiters++;
do pthread_cond_wait(&self->read, &self->lock);
while (self->writers || self->write_waiters);
self->read_waiters--;
}
self->readers++;
pthread_mutex_unlock(&self->lock);
}
void reader_unlock(struct rwlock *self) {
pthread_mutex_lock(&self->lock);
self->readers--;
if (self->write_waiters)
pthread_cond_signal(&self->write);
pthread_mutex_unlock(&self->lock);
}
void writer_lock(struct rwlock *self) {
pthread_mutex_lock(&self->lock);
if (self->readers || self->writers) {
self->write_waiters++;
do pthread_cond_wait(&self->write, &self->lock);
while (self->readers || self->writers);
self->write_waiters--;
}
self->writers = 1;
pthread_mutex_unlock(&self->lock);
}
void writer_unlock(struct rwlock *self) {
pthread_mutex_lock(&self->lock);
self->writers = 0;
if (self->write_waiters)
pthread_cond_signal(&self->write);
else if (self->read_waiters)
pthread_cond_broadcast(&self->read);
pthread_mutex_unlock(&self->lock);
}
void rwlock_init(struct rwlock *self) {
self->readers = self->writers = self->read_waiters = self->write_waiters = 0;
pthread_mutex_init(&self->lock, NULL);
pthread_cond_init(&self->read, NULL);
pthread_cond_init(&self->write, NULL);
}
這種實現方式可以在有寫鎖存在的情況下不增加讀鎖數量,而是累加讀等待數目。
三、一種Windows環境下的實現方法
在Windows環境下實現方式也是差不多的,參考如下實例:
typedef struct _RWLock
{
int count;
int state;
HANDLE hRead;
HANDLE hWrite;
} RWLock;
typedef enum /* 枚舉讀寫狀態 */
{
STATE_EMPTY = 0,
STATE_READ,
STATE_WRITE
};
RWLock* create_read_write_lock(HANDLE hRead, HANDLE hWrite)
{
RWLock* pRwLock = NULL;
assert(NULL != hRead && NULL != hWrite);
pRwLock = (RWLock*)malloc(sizeof(RWLock));
pRwLock->hRead = hRead;
pRwLock->hWrite = hWrite;
pRwLock->count = 0;
pRwLock->state = STATE_EMPTY;
return pRwLock;
}
void read_lock(RWLock* pRwLock)
{
assert(NULL != pRwLock);
WaitForSingleObject(pRwLock->hRead, INFINITE);
pRwLock->count ++;
if(1 == pRwLock->count){
WaitForSingleObject(pRwLock->hWrite, INFINITE);
pRwLock->state = STATE_READ;
}
ReleaseMutex(pRwLock->hRead);
}
void write_lock(RWLock* pRwLock)
{
assert(NULL != pRwLock);
WaitForSingleObject(pRwLock->hWrite, INFINITE);
pRwLock->state = STATE_WRITE;
}
void read_write_unlock(RWLock* pRwLock)
{
assert(NULL != pRwLock);
if(STATE_READ == pRwLock->state){
WaitForSingleObject(pRwLock->hRead, INFINITE);
pRwLock->count --;
if(0 == pRwLock->count){
pRwLock->state = STATE_EMPTY;
ReleaseMutex(pRwLock->hWrite);
}
ReleaseMutex(pRwLock->hRead);
}else{
pRwLock->state = STATE_EMPTY;
ReleaseMutex(pRwLock->hWrite);
}
return;
}
Windows環境下的實現方式要更加簡單一些,不過這種方式的寫操作一旦有讀操作獲取了鎖,就只能等待所有讀操作執行完了才行。如果一直都有讀操作,那麽寫操作將會一直等待下去。
四、讀寫鎖使用的經驗總結
(1)讀寫鎖的優勢只有在多讀少寫、代碼段運行時間長這兩個條件下才會效率達到最大化;
(2)任何公共數據的修改都必須在鎖裏面完成;
(3)讀寫鎖有自己的應用場所,選擇合適的應用環境十分重要;
(4)編寫讀寫鎖很容易出錯,需要多加練習;
(5)讀鎖和寫鎖一定要分開使用,否則達不到效果。
多線程編程之讀寫鎖