一 點睛
先看看互斥鎖,它只有兩個狀態,要麼是加鎖狀態,要麼是不加鎖狀態。假如現在一個線程a只是想讀一個共享變量 i,因爲不確定是否會有線程去寫它,所以我們還是要對它進行加鎖。但是這時又有一個線程b試圖去讀共享變量 i,發現被鎖定了,那麼b不得不等到a釋放了鎖後才能獲得鎖並讀取 i 的值,但是兩個讀取操作即使是同時發生的,也並不會像寫操作那樣造成競爭,因爲它們不修改變量的值。所以我們期望在多個線程試圖讀取共享變量的時候,它們可以立刻獲取因爲讀而加的鎖,而不是需要等待前一個線程釋放。
讀寫鎖可以解決上面的問題。它提供了比互斥鎖更好的並行性。因爲以讀模式加鎖後,當有多個線程試圖再以讀模式加鎖時,並不會造成這些線程阻塞在等待鎖的釋放上。
讀寫鎖是多線程同步的另外一個機制。在一些程序中存在讀操作和寫操作問題,對某些資源的訪問會存在兩種可能情況,一種情況是訪問必須是排他的,就是獨佔的意思,這種操作稱作寫操作,另外一種情況是訪問方式是可以共享的,就是可以有多個線程同時去訪問某個資源,這種操作稱爲讀操作。這個問題模型是從對文件的讀寫操作中引申出來的。把對資源的訪問細分爲讀和寫兩種操作模式,這樣可以大大增加併發效率。讀寫鎖比互斥鎖適用性更高,並行性也更高。
需要注意的是,這裏只是說並行效率比互斥高,並不是速度一定比互斥鎖快,讀寫鎖更復雜,系統開銷更大。併發性好對於用戶體驗非常重要,假設互斥鎖需要0.5秒,使用讀寫鎖需要0.8秒,在類似學生管理系統的軟件中,可能90%的操作都是查詢操作。如果突然有20個查詢請求,使用的是互斥鎖,則最後的查詢請求被滿足需要10秒,估計沒人接收。使用讀寫鎖時,因爲讀鎖能多次獲得,所以20個請求中,每個請求都能在1秒左右被滿足,用戶體驗好的多。
二 讀寫鎖特點
1 如果一個線程用讀鎖鎖定了臨界區,那麼其他線程也可以用讀鎖來進入臨界區,這樣可以有多個線程並行操作。這個時候如果再用寫鎖加鎖就會發生阻塞。寫鎖請求阻塞後,後面繼續有讀鎖來請求時,這些後來的讀鎖都將會被阻塞。這樣避免讀鎖長期佔有資源,防止寫鎖飢餓。
2 如果一個線程用寫鎖鎖住了臨界區,那麼其他線程無論是讀鎖還是寫鎖都會發生阻塞。
三 讀寫鎖使用的函數
操作
相關函數說明
初始化讀寫鎖
pthread_rwlock_init 語法
讀取讀寫鎖中的鎖
pthread_rwlock_rdlock 語法
讀取非阻塞讀寫鎖中的鎖
pthread_rwlock_tryrdlock 語法
寫入讀寫鎖中的鎖
pthread_rwlock_wrlock 語法
寫入非阻塞讀寫鎖中的鎖
pthread_rwlock_trywrlock 語法
解除鎖定讀寫鎖
pthread_rwlock_unlock 語法
銷燬讀寫鎖
pthread_rwlock_destroy 語法
讀寫鎖是用來解決讀者寫者問題的,讀操作可以共享,寫操作是排他的,讀可以有多個在讀,寫只有唯一個在寫,同時寫的時候不允許讀。
具有強讀者同步和強寫者同步兩種形式
強讀者同步:當寫者沒有進行寫操作,讀者就可以訪問;
強寫者同步:當所有寫者都寫完之後,才能進行讀操作,讀者需要最新的信息,一些事實性較高的系統可能會用到該所,比如定票之類的。
讀寫鎖的操作:
讀寫鎖的初始化:
定義讀寫鎖: pthread_rwlock_t m_rw_lock;
函數原型: pthread_rwlock_init(pthread_rwlock_t * ,pthread_rwattr_t *);
返回值:0,表示成功,非0爲一錯誤碼
讀寫鎖的銷燬:
函數原型: pthread_rwlock_destroy(pthread_rwlock_t* );
返回值:0,表示成功,非0表示錯誤碼
獲取讀寫鎖的讀鎖操作:分爲阻塞式獲取和非阻塞式獲取,如果讀寫鎖由一個寫者持有,則讀線程會阻塞直至寫入者釋放讀寫鎖。
阻塞式:
函數原型:pthread_rwlock_rdlock(pthread_rwlock_t*);
非阻塞式:
函數原型:pthread_rwlock_tryrdlock(pthread_rwlock_t*);
返回值: 0,表示成功,非0表示錯誤碼,非阻塞會返回ebusy而不會讓線程等待
獲取讀寫鎖的寫鎖操作:分爲阻塞和非阻塞,如果對應的讀寫鎖被其它寫者持有,或者讀寫鎖被讀者持有,該線程都會阻塞等待。
阻塞式:
函數原型:pthread_rwlock_wrlock(pthread_rwlock_t*);
非阻塞式:
函數原型:pthread_rwlock_trywrlock(pthread_rwlock_t*);
返回值: 0,表示成功
釋放讀寫鎖:
函數原型:pthread_rwlock_unlock(pthread_rwlock_t*);
總結(轉):
互斥鎖與讀寫鎖的區別:
當訪問臨界區資源時(訪問的含義包括所有的操作:讀和寫),需要上互斥鎖;
當對數據(互斥鎖中的臨界區資源)進行讀取時,需要上讀取鎖,當對數據進行寫入時,需要上寫入鎖。
讀寫鎖的優點:
對於讀數據比修改數據頻繁的應用,用讀寫鎖代替互斥鎖可以提高效率。因爲使用互斥鎖時,即使是讀出數據(相當於操作臨界區資源)都要上互斥鎖,而採用讀寫鎖,則可以在任一時刻允許多個讀出者存在,提高了更高的併發度,同時在某個寫入者修改數據期間保護該數據,以免任何其它讀出者或寫入者的干擾。
讀寫鎖描述:
獲取一個讀寫鎖用於讀稱爲共享鎖,獲取一個讀寫鎖用於寫稱爲獨佔鎖,因此這種對於某個給定資源的共享訪問也稱爲共享-獨佔上鎖。
有關這種類型問題(多個讀出者和一個寫入者)的其它說法有讀出者與寫入者問題以及多讀出者-單寫入者鎖。
————————————————
版權聲明:本文爲CSDN博主「不材之木」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/wonderisland/article/details/16940925
c++讀寫鎖實現
https://blog.csdn.net/zxc024000/article/details/88814461
C++17,提供了shared_mutex。配合C++14,提供的shared_lock。及C++11,提供的 unique_lock, 可以方便實現讀寫鎖。
但上述的前提是,允許你使用C++17。在國內的開發環境下,別說C++17,連C++11用的也不多。
所以,大多數時候,我們需要自己實現一套C++讀寫鎖(C++11環境下)。
RWLock.h
#ifndef RWLOCK__H
#define RWLOCK__H
#ifndef __cplusplus
# error ERROR: This file requires C++ compilation(use a .cpp suffix)
#endif
#include <mutex>
#include <condition_variable>
namespace linduo {
class RWLock {
public:
RWLock();
virtual ~RWLock() = default;
void lockWrite();
void unlockWrite();
void lockRead();
void unlockRead();
private:
volatile int m_readCount;
volatile int m_writeCount;
volatile bool m_isWriting;
std::mutex m_Lock;
std::condition_variable m_readCond;
std::condition_variable m_writeCond;
};
class ReadGuard {
public:
explicit ReadGuard(RWLock& lock);
virtual ~ReadGuard();
private:
ReadGuard(const ReadGuard&);
ReadGuard& operator=(const ReadGuard&);
private:
RWLock &m_lock;
};
class WriteGuard {
public:
explicit WriteGuard(RWLock& lock);
virtual ~WriteGuard();
private:
WriteGuard(const WriteGuard&);
WriteGuard& operator=(const WriteGuard&);
private:
RWLock& m_lock;
};
} /* namespace linduo */
#endif // RWLOCK__H
RWLock.cpp
#include "RWLock.h"
namespace linduo {
RWLock::RWLock()
: m_readCount(0)
, m_writeCount(0)
, m_isWriting(false) {
}
void RWLock::lockRead() {
std::unique_lock<std::mutex> gurad(m_Lock);
m_readCond.wait(gurad, [=] { return 0 == m_writeCount; });
++m_readCount;
}
void RWLock::unlockRead() {
std::unique_lock<std::mutex> gurad(m_Lock);
if (0 == (--m_readCount)
&& m_writeCount > 0) {
// One write can go on
m_writeCond.notify_one();
}
}
void RWLock::lockWrite() {
std::unique_lock<std::mutex> gurad(m_Lock);
++m_writeCount;
m_writeCond.wait(gurad, [=] { return (0 == m_readCount) && !m_isWriting; });
m_isWriting = true;
}
void RWLock::unlockWrite() {
std::unique_lock<std::mutex> gurad(m_Lock);
m_isWriting = false;
if (0 == (--m_writeCount)) {
// All read can go on
m_readCond.notify_all();
} else {
// One write can go on
m_writeCond.notify_one();
}
}
ReadGuard::ReadGuard(RWLock &lock)
: m_lock(lock) {
m_lock.lockRead();
}
ReadGuard::~ReadGuard() {
m_lock.unlockRead();
}
WriteGuard::WriteGuard(RWLock &lock)
: m_lock(lock) {
m_lock.lockWrite();
}
WriteGuard::~WriteGuard() {
m_lock.unlockWrite();
}
} /* namespace linduo */
使用
RWLock m_Lock;
void func() {
// 寫鎖
WriteGuard autoSync(m_Lock);
}
void func() {
// 讀鎖
ReadGuard autoSync(m_Lock);
}