1. 互斥鎖
之前講過使用Windows的API和Qt中如何創建和使用互斥鎖。接下來,主要說明一下C++11中的互斥鎖。
c++11中的互斥鎖主要有如下幾種:
互斥鎖 | 說明 |
---|---|
mutex | 最基本的互斥鎖,不可重入 |
timed_mutex | 具有超時機制的互斥鎖 |
recursive_mutex | 可重入的互斥鎖 |
recursive_timed_mutex | 結合 timed_mutex 和 recursive_mutex 特點的互斥量 |
他們都有加鎖(lock)、嘗試加鎖(trylock)和解鎖(unlock)的方法。
(1) 遞歸鎖和非遞歸鎖
這裏說明一下 遞歸鎖 和 非遞歸鎖
- 遞歸鎖 ,可重入鎖。表示同一線程中,可以多次加鎖,要解鎖相應的次數才能夠解鎖。
- 非遞歸鎖 ,不可重入。表示如果多次加鎖,會阻塞導致線程死鎖。
Windows API CreateCreateMutex 和臨界區都屬於 遞歸鎖 。
而Qt中的 QMutex 創建時默認是 非遞歸的鎖 ,構造中傳入參數 Recursive (可遞歸),如果不傳則默認爲 NonRecursive (非遞歸)。
接下來舉一個例子來說明:
std::mutex g_mutex;
int g_number = 0;
void func2(void);
void func(void)
{
g_mutex.lock();
++g_number;
func2();
g_mutex.unlock();
}
void func2(void)
{
g_mutex.lock();
++g_number;
g_mutex.unlock();
}
如果在線程中,調用函數 func() 就會引起線程 死鎖 ,因爲它加鎖兩次。
而使用 std::recursive_mutex 替換 std::mutex 就沒有死鎖的問題。
但是,這並不意味着,我們就需要使用 遞歸鎖 ,使用 非遞歸鎖 更容易定位我們程序中的問題。
(2) 互斥鎖管理類
之前我們在j講 QMutex 的時候,同時講解了RALL的 QMutexLocker ;C++11中也提供了類似的方法。
Qt中的互斥鎖可參照:線程的互斥和同步(4)- Qt中的互斥鎖(QMutex和QMutexLocker)
互斥鎖管理類 | 說明 |
---|---|
lock_guard | 基於作用域的互斥量管理 |
unique_lock | 更加靈活的互斥量管理 |
- lock_guard , 沒有 lock 和 unlock 僅提供構造時加鎖,析構時解鎖。
- unique_lock , 允許延遲鎖定、鎖定的有時限嘗試、遞歸鎖定、所有權轉移和與條件變量一同使用。
比如可以這麼使用一個互斥鎖管理器:
void func(void)
{
std::unique_lock<std::mutex> locker(g_mutex, std::defer_lock);
locker.lock();
++g_number;
}
locker 在構造的時候可以指定爲構造時加鎖還是不加鎖。
這裏使用 std::defer_lock 表明構造的時候不加鎖,使用 adopt_lock 表示構造的時候同時加鎖。
析構時,根據當前的鎖屬性判斷是否解鎖,如果當前爲加鎖狀態,析構時就解鎖。
unique_lock 它滿足可移動構造 (MoveConstructible) 和可移動賦值 (MoveAssignable) 但不滿足可複製構造 (CopyConstructible) 或可複製賦值 (CopyAssignable) 。
同時還有方法 swap (與另一 std::unique_lock 交換狀態) 和 release (將關聯互斥解關聯而不解鎖它)
關於 unique_lock 的更多使用,可參照:
https://zh.cppreference.com/w/cpp/thread/unique_lock
2. std::condition_variable
之前講了使用Qt創建和使用條件變量,C++11中也提供了對於條件變量的操作方法
線程的互斥和同步(7)- Qt的條件變量QWaitCondition
它同樣提供了wait和喚醒的方法
- 等待:wait 、wait_for 、 wait_until
- 喚醒:notify_one (喚醒一個) 和 notify_all (喚醒全部)
這裏需要注意的是 虛假喚醒 :
虛假喚醒 是指可能一個線程沒有發送喚醒通知,但是該線程已經被喚醒。
通常防止虛假喚醒的做法是,使用一個 while 循環,如果是虛假喚醒,則繼續等待:
std::mutex g_mutex;
std::condition_variable var;
bool g_pred = false;
void func(void)
{
std::unique_lock<std::mutex> locker(g_mutex);
// 循環等待,防止虛假喚醒
while (!g_pred)
{
// 當喚醒時,g_pred 將設置爲true
var.wait(locker);
}
}
也可以寫成如下方式:
std::mutex g_mutex;
std::condition_variable var;
bool g_pred = false;
void func(void)
{
std::unique_lock<std::mutex> locker(g_mutex);
// 防止虛假喚醒
var.wait(locker, [] {return g_pred; });
}
此處的 wait 函數的第二個參數,爲一個仿函數(lambda表達式就是一個匿名仿函數),如果返回值爲 true 則忽略虛假喚醒;否則繼續等待。
關於 std::condition_variable 更多介紹,可參考:
https://zh.cppreference.com/w/cpp/thread/condition_variable
作者:douzhq
個人博客:不會飛的紙飛機
文章同步頁:線程的互斥和同步(8)- C++11中的互斥鎖和條件變量