std::mutex和lock系列


1. std::mutex:獨占的互斥量,不能遞歸使用。下面是它的類的部分定義:

class mutex
{
public:
    // std::mutex不支持拷貝和賦值操作。
    mutex(const mutex&) = delete;
    mutex& operator=(const mutex&) = delete;
    constexpr mutex() noexcept; // 構造函數:新的對象是未鎖的
    ~mutex();

public:
    void lock();      // 上鎖。會有三種情況
                      // (1) 如果該互斥量當前沒有被鎖住,則調用線程將該互斥量鎖住,直到調用unlock之前,該線程一直擁有該鎖
                      // (2) 如果當前互斥量被其他線程鎖住,則當前的調用線程被阻塞住
                      // (3)如果當前互斥量被當前調用線程鎖住,則會產生死鎖(deadlock)。
    void unlock();    // 解鎖
    bool try_lock();  // 嘗試上鎖。成功,返回true。失敗時返回false,但不阻塞。會有三種情況
                      // (1) 如果當前互斥量沒被其他線程占有,則鎖住互斥量,直到該線程調用unlock
                      // (2) 如果當前互斥量被其他線程占用,則調用線程返回false,且不會被阻塞
                      // (3) 如果互斥量己被當前線程鎖住,則會產生死鎖
};

  為什么有些類會去禁止拷貝和賦值呢?主要是防止淺拷貝的問題,因為類中如果有指針的話,淺拷貝方式的結果是兩個不同對象的指針

    指向同一塊內存區域,容易出現訪問沖突,多次delete等錯誤,不是我們所希望的。

    1)互斥量不允許拷貝,也不允許移動。新創建的互斥量對象是未上鎖的。

    2)lock和unlock必須成對出現,否則可能引起未定義行為。

    注:實踐中不推薦直接去調用成員函數lock(),調用lock()就意味着,必須在每個函數出口都要去調用unlock(),也包括異常的情況。

        如果程序員沒有進行unlock或者因為異常無法unlock,那么系統就會發生死鎖。針對這個問題,C++11中引入了std::unique_lock

        與std::lock_guard兩種數據結構。通過對lock和unlock進行一次薄的封裝(只是包裝,真正的加鎖和解鎖還都是mutex完成的),

        實現自動unlock的功能。

 

 2. std::lock_guard:C++標准庫為互斥量提供了一個RAII語法的模板類std::lock_guard,在構造時就能提供已鎖的互斥量,並在析構的時候

    進行解鎖,從而保證了一個已鎖互斥量能被正確解鎖(自解鎖),不會因為某個線程異常退出而影響其他線程。下面是它的類的部分定義。

struct adopt_lock_t {};                // 空的標記類
constexpr adopt_lock_t adopt_lock {};  // 常量對象

template <class _Mutex>
class lock_guard 
{
public:
    using mutex_type = _Mutex;
    explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { _MyMutex.lock(); }  // 構造,並加鎖
    lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) {}               // 只構造,不加鎖
    ~lock_guard() noexcept { _MyMutex.unlock(); }                            // unlock
    lock_guard(const lock_guard&) = delete;
    lock_guard& operator=(const lock_guard&) = delete;

private:
    _Mutex& _MyMutex;
};

  1)lock_guard對象不可拷貝和移動

    2)它有兩個重載的構造函數,其中lock_gurad(_Mutex&)會自動對_Mutex進行加鎖,而lock_gurad(_Mutex&,adopt_lock_t)則只構造

       但不加鎖此需要在某個時候通過調用_Mutex本身的lock()進行上鎖。(說明:adopt_lock_t是個空的標簽類,起到通過標簽來重載構造函數的作用)。

    3)在lock_gurad對象的生命周期內,它所管理的Mutex對象會一直保持上鎖狀態,直至生命周期結束后才被解鎖。不需要,也無法手動通過lock_gurad對

       Mutex進行上鎖和解鎖操作。從總體上而言,沒有給程序員提供足夠的靈活度來對互斥量的行上鎖和解鎖控制。

 

3. std::unique_lock:std::unique_lock 與std::lock_guard都能實現自動加鎖與解鎖功能,但是std::unique_lock要比std::lock_guard更

   靈活,但是更靈活的代價是占用空間相對更大一點且相對更慢一點。下面是它的類的部分定義:

// 空的標記類
struct adopt_lock_t {};
struct defer_lock_t {};
struct try_to_lock_t {};
 
// 常量對象
constexpr adopt_lock_t adopt_lock {};
constexpr defer_lock_t defer_lock {};
constexpr try_to_lock_t try_to_lock {};
 
template <class _Mutex>
class unique_lock { // 在析構函數中自動解鎖mutex
public:
    using mutex_type = _Mutex;
    unique_lock() noexcept : _Pmtx(nullptr), _Owns(false) { // 默認構造函數
    }
    
    explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // 構造並上鎖。
        _Pmtx->lock(); // 如果其他unique_lock己擁有該_Mtx,則會阻塞等待
        _Owns = true;  // 成功獲取鎖,擁有鎖的所有權。
    }

    unique_lock(_Mutex& _Mtx, adopt_lock_t)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(true) {  // 構造,並假定己上鎖(mutex需要在外面事先被鎖住)。注意擁有鎖的所有權
    }

    unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept
        : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // 構造,但不上鎖。false表示並未取得鎖的所有權。
    }

    unique_lock(_Mutex& _Mtx, try_to_lock_t)
        : _Pmtx(_STD addressof(_Mtx)), _Owns(_Pmtx->try_lock()) { // 構造,並嘗試上鎖。如果上鎖不成功,並不會阻塞當前線程
    }

    //支持移動構造
    unique_lock(unique_lock&& _Other) noexcept : _Pmtx(_Other._Pmtx), _Owns(_Other._Owns) { // 移動拷貝,destructive copy
        _Other._Pmtx = nullptr; // 失去對原mutex的所有權
        _Other._Owns = false;
    }

    //支持移動賦值
    unique_lock& operator=(unique_lock&& _Other) { // 移動賦值, destructive copy
        if (this != _STD addressof(_Other)) {      // different, move contents
            if (_Owns) {
                _Pmtx->unlock();
            }
            _Pmtx        = _Other._Pmtx;
            _Owns        = _Other._Owns;
            _Other._Pmtx = nullptr;
            _Other._Owns = false;
        }
        return *this;
    }

    ~unique_lock() noexcept { // clean up
        if (_Owns) {
            _Pmtx->unlock();  // 析構函數中解鎖
        }
    }

    unique_lock(const unique_lock&) = delete;
    unique_lock& operator=(const unique_lock&) = delete;
 
    void lock() { // lock the mutex
        _Validate();
        _Pmtx->lock();
        _Owns = true;
    }

    _NODISCARD bool try_lock() { // try to lock the mutex
        _Validate();
        _Owns = _Pmtx->try_lock();
        return _Owns;
    }

    void unlock() {              // try to unlock the mutex
        if (!_Pmtx || !_Owns) {
            _THROW(system_error(_STD make_error_code(errc::operation_not_permitted)));
        }
        _Pmtx->unlock();
        _Owns = false;
    }

    void swap(unique_lock& _Other) noexcept { // swap with _Other
        _STD swap(_Pmtx, _Other._Pmtx);
        _STD swap(_Owns, _Other._Owns);
    }

    _Mutex* release() noexcept { // 返回指向它所管理的 Mutex 對象的指針,並釋放所有權
        _Mutex* _Res = _Pmtx;
        _Pmtx        = nullptr;
        _Owns        = false;
        return _Res;
    }

    _NODISCARD bool owns_lock() const noexcept { return _Owns; } // 返回當前 std::unique_lock 對象是否獲得了鎖
    explicit operator bool() const noexcept { return _Owns; }    // 返回當前 std::unique_lock 對象是否獲得了鎖
    _NODISCARD _Mutex* mutex() const noexcept { return _Pmtx; }  // return pointer to managed mutex

private:
    _Mutex* _Pmtx;
    bool _Owns; // 是否擁有鎖(當mutex被lock時,為true;否則為false)
};

    1)以獨占所有權的方式管理Mutex對象的上鎖和解鎖操作,即沒有其他的unique_lock對象同時擁有某個Mutex對象的所有權。

  2)與std::lock_guard一樣,在unique_lock生命期結束后,會對其所管理的Mutex進行解鎖。(注意:unique_lock只對擁有所有權的mutex才會在析構函數中被自動unlock)。

  3)這里再介紹下unique_lock的構造函數:

     a. unique_lock()默認構造函數:新創建的unique_lock對象不管理任何Mutex對象。

     b. unique_lock(_Mutex& m):構造並上鎖。如果此時某個另外的unique_lock己管理m對象,則當前線程會被阻塞。

     c. unique_lock(_Mutex& m, adopt_lock_t):構造,並假定m己上鎖。(m需要事先被上鎖,構造結束后unique_lock就擁有m的所有權

     d. unique_lock(_Mutex& _Mtx, defer_lock_t):構造,但不上鎖。對象創建以后,可以手動調用unique_lock的lock來上鎖,才擁有_Mtx的所有權。

          強調一下,只有擁有所有權的mutex才會在析構函數中被自動unlock。

     e. unique_lock(_Mutex& _Mtx, try_to_lock_t):構造,並嘗試上鎖。如果上鎖不成功,並不會阻塞當前線程。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM