跳至內容

C++/Mutex

維基教科書,自由的教學讀本
< C++

mutexC++標準程式庫中的一個頭文件,定義了C++11標準中的一些互斥訪問的類與方法等。

互斥類[編輯]

標準庫中的互斥鎖類型可分為:

  • 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;
}

參考文獻[編輯]