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):構造,並嘗試上鎖。如果上鎖不成功,並不會阻塞當前線程。