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