C++/Mutex
互斥類
編輯標準庫中的互斥鎖類型可分為:
- BasicLockable類型,只需滿足兩種操作,lock 和 unlock
- Lockable 類型,在BasicLockable類型的基礎上增加了try_lock操作
- TimedLockable類型,在Lockable類型的基礎上增加了try_lock_for 和 try_lock_until 兩種操作。
標準庫定義了4個互斥類:
- std::mutex:該類表示普通的b:互斥鎖。類成員包括:
- 構造函數,不允許拷貝構造,也不允許move構造,mutex對象初始化為unlocked狀態;
- 成員函數:
- lock():線程申請該互斥鎖。如果未能獲得該互斥鎖,則調用線程將阻塞(block)在該互斥鎖上;如果成功獲得該互訴鎖,該線程一直擁有該互斥鎖直到調用unlock解鎖;如果該互斥鎖已經被當前調用線程鎖住,則產生死鎖(deadlock)。
- unlock():解鎖,釋放調用線程對該互斥鎖的所有權。
- try_lock():嘗試獲得該互斥鎖。如果互斥鎖被其他線程佔有,則當前調用線程也不會被阻塞,而是由該函數調用返回false;如果該互斥鎖已經被當前調用線程鎖住,則會產生死鎖。
- std::timed_mutex:該類表示定時互斥鎖。std::time_mutex比std::mutex多了兩個成員函數:
- try_lock_for():函數參數表示一個時間範圍,在這一段時間範圍之內線程如果沒有獲得鎖則保持阻塞;如果在此期間其他線程釋放了鎖,則該線程可獲得該互斥鎖;如果超時(指定時間範圍內沒有獲得鎖),則函數調用返回false。
- try_lock_until():函數參數表示一個時刻,在這一時刻之前線程如果沒有獲得鎖則保持阻塞;如果在此時刻前其他線程釋放了鎖,則該線程可獲得該互斥鎖;如果超過指定時刻沒有獲得鎖,則函數調用返回false。
- std::recursive_mutex:該類表示遞歸互斥鎖。遞歸互斥鎖可以被同一個線程多次加鎖,以獲得對互斥鎖對象的多層所有權。例如,同一個線程多個函數訪問臨界區時都可以各自加鎖,執行後各自解鎖。std::recursive_mutex釋放互斥量時需要調用與該鎖層次深度相同次數的unlock(),即lock()次數和unlock()次數相同。可見,線程申請遞歸互斥鎖時,如果該遞歸互斥鎖已經被當前調用線程鎖住,則不會產生死鎖。此外,std::recursive_mutex的功能與 std::mutex大致相同。
- recursive_timed_mutex:定時遞歸 Mutex 類
用於互斥鎖的RAII的類模板
編輯互斥類的最重要成員函數是lock() 和 unlock() 。在進入b:臨界區時,執行lock()加鎖操作,如果這時已經被其它線程鎖住,則當前線程在此排隊等待。退出臨界區時,執行unlock()解鎖操作。
更好的辦法是採用「資源分配時初始化」(RAII)方法來加鎖、解鎖,這避免了在臨界區中因為拋出異常或return等操作導致沒有解鎖就退出的問題。極大地簡化了程式設計師編寫Mutex相關的異常處理代碼。
lock_guard類模板
編輯C++11的標準庫中提供了lock_guard類模板做mutex的RAII,
template <class Mutex> class lock_guard;
模板參數Mutex是一個BasicLockable類型,標準庫中定義的BasicLockable類型有:std::mutex, std::recursive_mutex, std::timed_mutex,std::recursive_timed_mutex 例如:
void foo( int n )
{
lock_guard<mutex> mLock( gMutex ); //gMutex是个全局变量
//mLock对象创建后,自动加锁
// Do something.......
//mLock对象生命期结束时,析构时自动解锁
}
- 成員函數
- lock_guard (mutex_type& m); 在構造時對互斥鎖對象m進行上鎖,通過調用 m.lock()
- lock_guard (mutex_type& m, adopt_lock_t tag); 構造函數「收養」(開始管理)已被當前線程加鎖的Mutex對象,此後mutex對象在lock_guard對象析構時自動解鎖
- lock_guard (const lock_guard&) = delete; 禁用拷貝構造,且禁用移動構造。
unique_lock類模板
編輯unique_lock 對象以獨佔所有權的方式( unique owership)管理 mutex 對象的上鎖和解鎖操作,即在unique_lock 對象的聲明周期內,它所管理的鎖對象會一直保持上鎖狀態;而 unique_lock 的生命周期結束之後,它所管理的鎖對象會被解鎖。unique_lock具有lock_guard的所有功能,而且更為靈活。雖然二者的對象都不能複製,但是unique_lock可以移動(movable),因此用unique_lock管理互斥對象,可以作為函數的返回值,也可以放到STL的容器中。
unique_lock還支持同時鎖定多個 mutex,這避免了多道加鎖時的資源「死鎖」問題。如下例所示:
// don't actually take the locks yet
std::unique_lock<std::mutex> lock1(mutex1, std::defer_lock);
std::unique_lock<std::mutex> lock2(mutex2, std::defer_lock);
// lock both unique_locks without deadlock
std::lock(lock1, lock2);
// Do something......
lock1.unlock();
lock2.unlock();
上例中,為了RAII的目的,在同時對兩個鎖加鎖之後,還可以追加兩條語句:
lock_guard<mutex> lock1( mutex1, adopt_lock );
lock_guard<mutex> lock2( mutex2, adopt_lock );
其中的adopt_lock常量參數是表示該鎖已經成功獲得。這樣當程序退出臨界區時,不用顯式執行解鎖操作,而是由兩個lock_guard在析構時自動解鎖。
- 構造函數
- unique_lock() noexcept; 默認構造函數,不管理任何 Mutex 對象。
- explicit unique_lock(mutex_type& m); 管理Mutex對象m,並調用m.lock()進行上鎖,可被阻塞在互斥鎖上
- unique_lock(mutex_type& m, try_to_lock_t tag);如果上鎖不成功,並不會阻塞當前線程。
- unique_lock(mutex_type& m, defer_lock_t tag) noexcept;構造函數並不對Mutex對象申請上所
- unique_lock(mutex_type& m, adopt_lock_t tag);「收養」管理一個已經上鎖的互斥鎖對象
- template <class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time);通過調用 m.try_lock_for(rel_time)獲取互斥鎖
- template <class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);通過調用 m.try_lock_until(abs_time)獲取互斥鎖
- unique_lock(const unique_lock&) = delete;禁止拷貝構造
- unique_lock(unique_lock&& x);允許移動構造
- 運算符
- unique_lock& operator= (unique_lock&& x) noexcept;允許移動賦值
- unique_lock& operator= (const unique_lock&) = delete;禁止拷貝賦值
- operator bool():返回當前 std::unique_lock 對象是否獲得了鎖
- 成員函數
- lock():調用所管理的 Mutex 對象的 lock 函數
- try_lock():調用所管理的 Mutex 對象的 try_lock 函數
- try_lock_for():調用所管理的 Mutex 對象的 try_lock_for 函數
- try_lock_until():調用所管理的 Mutex 對象的 try_lock_until 函數
- unlock():調用所管理的 Mutex 對象的 unlock 函數
- release():返回所管理的 Mutex 對象的指針,並釋放所有權。但不改變Mutex對象的狀態
- owns_lock():返回當前 std::unique_lock 對象是否獲得了鎖
- mutex():返回當前 std::unique_lock 對象所管理的 Mutex 對象的指針
其他數據類型
編輯- std::once_flag
- std::adopt_lock_t:一個空的標記類。通常作為參數傳入給 unique_lock 或 lock_guard 的構造函數,用於「收養」一個早已被加鎖的mutex對象。
- std::defer_lock_t:一個空的標記類。該類型的常量對象defer_lock通常作為參數傳入給 unique_lock 或 lock_guard 的構造函數。
- std::try_to_lock_t:一個空的標記類。該類型的常量對象try_to_lock通常作為參數傳入給 unique_lock 或 lock_guard 的構造函數。
函數
編輯- std::try_lock():嘗試同時對多個互斥量上鎖。
- std::lock():可以同時對多個互斥量上鎖。
- std::call_once():如果多個線程需要同時調用某個函數,call_once 可以保證多個線程對該函數隻調用一次。
例子程序
編輯#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex gMutex;
void foo( int n )
{
gMutex.lock();
cout << "Something...."<<n<<endl;
gMutex.unlock();
}
int main( int argc, char** argv )
{
thread mThread1( foo, 3 );
thread mThread2( foo, 4 );
mThread1.join();
mThread2.join();
return 0;
}