無鎖程式設計:c++11基於atomic實現共享讀寫鎖(寫優先)
阿新 • • 發佈:2019-01-08
在多執行緒狀態下,對一個物件的讀寫需要加鎖,基於CAS指令的原子語句可以實現高效的執行緒間協調。關於CAS的概念參見下面的文章:
在c++11中CAS指令已經被封裝成了 非常方便使用的atomic模板類, 詳情參見:
以下程式碼利用atomic
實現了一個讀寫資源鎖,並且可以根據需要通過建構函式引數設定成寫優先(write_first)(程式碼在gcc5和vs2015下編譯通過):
readLock/Unlock 實現共享的讀取加/解鎖,執行緒數不限,有讀取執行緒工作時,所有的申請寫入執行緒都會等待
writeLock/Unlock 實現獨佔的寫入加/解鎖,同時只允許一個執行緒寫入,當有執行緒在讀取時,寫入執行緒等待,當寫入執行緒執行時,所有的讀取執行緒都被等待。
locck/unlock語句允許巢狀
比如
lock.readLock();
lock.readLock();
...
lock.readUnlock();
lock.readUnlock();
也允許在寫入狀態下巢狀讀取,比如
lock.writeLock();
lock.writeLock();
lock.readLock();
...
lock.readUnlock();
lock.writeUnlock();
lock.writeUnlock();
RWLock.h
#include <cstdlib>
#include <cassert>
#include <atomic>
#include <thread>
#include "raii.h"
/*
* atomic實現讀寫資源鎖,獨佔寫,共享讀,禁止複製建構函式和'='賦值操作符
* WRITE_FIRST為true時為寫優先模式,如果有執行緒等待讀取(m_writeWaitCount>0)則等待,優先讓寫執行緒先獲取鎖
* 允許巢狀加鎖
* readLock/Unlock 實現共享的讀取加/解鎖,執行緒數不限
* writeLock/Unlock 實現獨佔的寫入加/解鎖,同時只允許一個執行緒寫入,
* 當有執行緒在讀取時,寫入執行緒阻塞,當寫入執行緒執行時,所有的讀取執行緒都被阻塞。
*/
class RWLock {
#define WRITE_LOCK_STATUS -1
#define FREE_STATUS 0
private:
/* 初始為0的執行緒id */
static const std::thread::id NULL_THEAD;
const bool WRITE_FIRST;
/* 用於判斷當前是否是寫執行緒 */
thread::id m_write_thread_id;
/* 資源鎖計數器,型別為int的原子成員變數,-1為寫狀態,0為自由狀態,>0為共享讀取狀態 */
atomic_int m_lockCount;
/* 等待寫執行緒計數器,型別為unsigned int的原子成員變數*/
atomic_uint m_writeWaitCount;
public:
// 禁止複製建構函式
RWLock(const RWLock&) = delete;
// 禁止物件賦值操作符
RWLock& operator=(const RWLock&) = delete;
//RWLock& operator=(const RWLock&) volatile = delete;
RWLock(bool writeFirst=false);;//預設為讀優先模式
virtual ~RWLock()=default;
int readLock();
int readUnlock();
int writeLock();
int writeUnlock();
// 將讀取鎖的申請和釋放動作封裝為raii物件,自動完成加鎖和解鎖管理
raii read_guard()const noexcept{
return make_raii(*this,&RWLock::readUnlock,&RWLock::readLock);
}
// 將寫入鎖的申請和釋放動作封裝為raii物件,自動完成加鎖和解鎖管理
raii write_guard()noexcept{
return make_raii(*this,&RWLock::writeUnlock,&RWLock::writeLock);
}
};
RWLock.cpp
RWLock::RWLock(bool writeFirst):
WRITE_FIRST(writeFirst),
m_write_thread_id(),
m_lockCount(0),
m_writeWaitCount(0){
}
int RWLock::readLock() {
// ==時為獨佔寫狀態,不需要加鎖
if (this_thread::get_id() != this->m_write_thread_id) {
int count;
if (WRITE_FIRST)//寫優先模式下,要檢測等待寫的執行緒數為0(m_writeWaitCount==0)
do {
while ((count = m_lockCount) == WRITE_LOCK_STATUS || m_writeWaitCount > 0);//寫鎖定時等待
} while (!m_lockCount.compare_exchange_weak(count, count + 1));
else
do {
while ((count = m_lockCount) == WRITE_LOCK_STATUS); //寫鎖定時等待
} while (!m_lockCount.compare_exchange_weak(count, count + 1));
}
return m_lockCount;
}
int RWLock::readUnlock() {
// ==時為獨佔寫狀態,不需要加鎖
if (this_thread::get_id() != this->m_write_thread_id)
--m_lockCount;
return m_lockCount;
}
int RWLock::writeLock(){
// ==時為獨佔寫狀態,避免重複加鎖
if (this_thread::get_id() != this->m_write_thread_id){
++m_writeWaitCount;//寫等待計數器加1
// 沒有執行緒讀取時(加鎖計數器為0),置為-1加寫入鎖,否則等待
for(int zero=FREE_STATUS;!this->m_lockCount.compare_exchange_weak(zero,WRITE_LOCK_STATUS);zero=FREE_STATUS);
--m_writeWaitCount;//獲取鎖後,計數器減1
m_write_thread_id=this_thread::get_id();
}
return m_lockCount;
}
int RWLock::writeUnlock(){
if(this_thread::get_id() != this->m_write_thread_id){
throw runtime_error("writeLock/Unlock mismatch");
}
assert(WRITE_LOCK_STATUS==m_lockCount);
m_write_thread_id=NULL_THEAD;
m_lockCount.store(FREE_STATUS);
return m_lockCount;
}
const std::thread::id RWLock::NULL_THEAD;
說明1
atomic_int,atomic_uint
都是從atomic
類模板中派生出來的類,對應不同的資料型別
atomic是c++11標準,在gcc編譯的時候必須加入std=c++11選項才能正確編譯,,vs編譯至少要用vs2012,因為visual studio 2012以上才支援atomic模板
說明2
如果按照預設的類定義方法,提供複製建構函式和賦值操作符=
,那麼可以想見,在應用中可能會產生不可預知的問題,所以參照atomic
模板的寫法,加入了禁止複製建構函式和物件複製操作符=
的程式碼,
//禁止複製建構函式
RWLock(const RWLock&) = delete;
//禁止物件賦值操作符
RWLock& operator=(const RWLock&) = delete;
RWLock& operator=(const RWLock&) volatile = delete;
說明3
這個程式碼還有欠缺的地方就是沒有實現超時異常中止。
說明4
read_guard,write_guard
函式返回的raii
類參見我的另一篇部落格《C++11實現模板化(通用化)RAII機制》