思維導圖
一些收獲
條件變量的虛假喚醒(spurious wakeup)
使用條件變量,可以讓線程等待某個條件,從而進入睡眠,當由其他線程所控制的布爾表達式滿足條件時,再由操作系統將其喚醒。條件變量的使用需要搭配一個互斥器,有一套幾乎不變的范式
// wait端
Mutex mutex;
Condition cond(mutex);
{
MutexLockGuard lock(mutex);
while(bool-expression)
{
cond.wait(); // 1. 執行wait()發生了什么?
}
// some operation after wake up
}
//2. wait端改成這樣可以嗎:
{
if(bool-expression)
{
cond.wait();
}
// some operation after wake up
}
// signal端
Mutex mutex;
{
MutexLockGuard lock(mutex);
modity-bool-expression;
cond.notify();
}
上面的代碼提出了兩個問題。先說問題1,條件變量在初始化時,就和一個互斥量綁定(注意這個互斥量既被用於MutexLockGuard 保護布爾表達式也用於與Condition綁定)。進入wait()后,條件變量會釋放已經獲取到的鎖,然后使當前線程進入睡眠(當然釋放是必須的,要是不釋放就陷入睡眠,那這個布爾表達式永遠也得不到修改)。生產者線程此時可以獲取鎖,並且改變這個布爾表達式,改變結束后將調用cond.notify(),喚醒消費者。消費者此時還在wait()中但即將返回,wait()函數在返回時,需要重新獲取鎖以獲得對布爾表達式的獨占。
再說問題2,使用if判斷是不行的,使用while循環的原因就是所謂的防止虛假喚醒。直觀的來講,條件變量的使用場景可能讓人無意有了“一個消費者,一個生產者”的定勢(因為這操作系統教科書里的經典例子)。
當消費者和生產者的數量都為1的時候,那么使用if語句是沒有問題的。但考慮“一個生產者,多個消費者”的情況,使用if語句判斷當由其他線程所控制的布爾表達式就會出現問題:如果此時線程A、B同時被喚醒,A線程先被喚醒,B線程后被喚醒。B線程已經搶先一步修改了布爾表達式,執行了相應邏輯,但此時線程A可能剛剛從wait()中返回,便在沒有檢查“最新情況下”的布爾表達式的情況下,錯誤的執行不該執行的邏輯。視布爾表達式的情況,可能會導致程序crash(比如布爾表達式是vector的empty(),就會造成內存錯誤)。
條件變量解決了什么問題
一言以蔽之,由輪詢轉化為被動等待。
不使用條件變量的情況:
// Thread Producer
produce () {
mutex_lock()
count++
mutex_unlock()
}
// Thread A
celebrateAfter100 () {
while (1) {
mutex_lock()
if (count >= 100) {
mutex_unlock()
break
}
mutex_unlock()
sleep(100)
}
// Celebrate!
}
使用條件變量的情況:
// Thread Producer
produce () {
mutex_lock()
count++
mutex_unlock()
if (count >= 100)
cond_signal(condition) // 條件滿足啦,通知一個等待的線程
}
// Thread A
celebrateAfter100 () {
mutex_lock()
while(count < 100)
cond_wait(condition) // 等到條件滿足再繼續執行
mutex_unlock()
// Celebrate!
}
Double-Check-Lock的單例問題在哪?
先要明確的一點,線程安全的單例模式只關注獲取資源的時候是否存在race condition,比如這樣的代碼:
Instance *GetInstance()
{
if(!p)
p = new Instance();
return p;
}
在多線程環境下是不安全的,可能導致內存泄露,但是線程安全的單例模式並不關注線程安全地獲取資源(唯一實例的地址)后,實例的成員的線程安全問題。
經典的單例有很多種,餓漢式/懶漢式(這名字可真是取的……一言難盡,就不能文雅一點嗎?)。
餓漢式單例也就是Meyer's Singleton模式,雖然不同編譯單元中,非local static變量存在初始化順序不確定的問題(Effective C++ 條款4),但是對於函數內部的static變量,C++可以保證在函數調用時,內部的static變量必被初始化,而這個資源的獲取是線程安全的,所以Meyer's Singleton可以放心的返回local static變量的地址或引用,實例如下:
Instance *GetInstance()
{
static Instance instance;
return &instance;
}