---
# System prepended metadata

title: 技術｜C++｜C++ Conditional Variable 討論
tags: [技術]

---

# C++ Conditional Variable 討論
- [RAII 機制](https://zh.wikipedia.org/zh-tw/RAII)
    - `unique_lock<mutex> ulocker(mu);` 會去要 `mu` 這把 lock，要不到的話會馬上 block 住直到要到為止才執行下一行，而要到的 lock 會在離開該 scope 之後自動 `lock.unlock()`
- `cond.wait(ulocker);` 
    - 可以拆成 `"unlock + wait" -> "wake up" -> "lock"` ，三件事分別 atomic
    - 因為 "unlock + wait" 是 atomic 因此不會造成 deadlock
    - 而被 notify 之後會去要 lock，也有可能當下要不到，但可以保證 Conditional Variable 的另一件事情已經做完
- `cond.wait(ulocker, condition);`
    - 先檢查 condition
        - 成立: 直接通過
        - 不成立: "unlock + wait" -> "wake up" -> "lock" -> 再檢查一次 condition
    ```c=
    // 以下兩者等價
    cond.wait(ulocker,[=](){return c==1;});
    while(c!=1){
        cond.wait(ulocker);
    }
    ```
- 如果 t2 只用 `cond.wait(ulocker);` ，不能控制流程，因為無法保證 t1 在執行 `notify_all()` 的時候，t2 已經要到 `mu` 這把 lock 並且執行 `cond.wait(ulocker);`，若還沒則之後 t2 會被 block 住
    ```clike=
    mutex mu;
    condition_variable cond;
    void fun1(){
        unique_lock<mutex> ulocker(mu);
        cout<<"111"<<endl;
        //mu.unlock();
        cond.notify_all();
    }
    void fun2(){
        unique_lock<mutex> ulocker(mu);
        cond.wait(ulocker);
        cout<<"222"<<endl;
    }
    ```
- 只單純使用 `cond.wait(ulocker);` ，應該無法正確控制流程，底下有兩個可能解法
    - 解法一: 另外使用全域變數 c，考慮 t2 取得 lock 的瞬間
        - 如果 t1 已經執行完，則 `c == 1` ，t2 直接往下執行
        - 如果 t1 都還沒執行，則 `c == 0` ，t2 先 `lock.unlock()` 等候通知，這時候 f1 才有機會進入 critical section 執行 `cond.notify_all();`
        - 其實 `while` 改成 `if` 也會對，但是 SPEC 說 `"wait for notify"` 的狀態可能會出現  [`spurious wake-up calls`](https://en.wikipedia.org/wiki/Spurious_wakeup) 而突然被 notify，因此用 `while` 迴圈檢查 t1 真的做完事情
    ```clike=
    int c = 0;
    mutex mu;
    condition_variable cond;
    void fun1(){
        unique_lock<mutex> ulocker(mu);
        cout<<"111"<<endl;
        c = 1;
        cond.notify_all();
    }
    void fun2(){
        unique_lock<mutex> ulocker(mu);
        //cond.wait(ulocker,[=](){return c==1;}); 和底下等價
        while(c!=1){
            cond.wait(ulocker);
        }
        cout<<"222"<<endl;
    }
    ```
    - 解法二: 讓 t1 執行 `lock.unlock()` 並睡 0.05 秒才通知 t2，有很大的可能性 t1 在 `cond.notify_all();` 時， t2 已經執行 `cond.wait(ulocker);`，因此程式可以順利控制流程
        - 缺點是雙方都浪費時間，實務上也不知道要睡多久
    ```clike=
    void fun1(){
        unique_lock<mutex> ulocker(mu);
        cout<<"111"<<endl;
        mu.unlock();
        Sleep(50);
        cond.notify_all();
    }
    void fun2(){
        unique_lock<mutex> ulocker(mu);
        cond.wait(ulocker);
        cout<<"222"<<endl;
    }
    ```
    - 嘗試解三: 對 condition variable 不熟的時候可以用 busy waiting
        - 使用 volatile 避免編譯器最佳化去掉 while 迴圈
        - 缺點: 程式會佔去絕大多數的 CPU 時間
    ```clike=
    volatile int c = 0;
    void fun1(){
        cout<<"111"<<endl;
        c = 1;
    }
    void fun2(){
        while(c==0){    }
        cout<<"222"<<endl;
    }
    ```
- 討論
    - 一、原本以為解法一也是 busy waiting ，但其實沒有
    - 二、原本以為 `cond.wait(ulocker);` 就能控制流程，但至少要搭配全域變數 `c`
    

###### tags: `筆記` `C++` `Conditional Variable`