陳碩Linux多線程服務端編程讀書筆記


思維導圖

(新窗口打開可以看大圖)

一些收獲

條件變量的虛假喚醒(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;
}

Copy-On-Write(寫時復制)手法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM