互斥鎖與讀寫鎖的概念

一 點睛

先看看互斥鎖,它只有兩個狀態,要麼是加鎖狀態,要麼是不加鎖狀態。假如現在一個線程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);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章