1. 程式人生 > >無鎖程式設計:c++11基於atomic實現共享讀寫鎖(寫優先)

無鎖程式設計:c++11基於atomic實現共享讀寫鎖(寫優先)

在多執行緒狀態下,對一個物件的讀寫需要加鎖,基於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機制》