2018年8月21日

[C++] Use std::condition_variable for Parallellism

C++11 開始 STL 就提供了 thread library以便寫平行化的程式,condition variable 簡單來說就是用來控制流程用的,他允許我們在過程中透過 condition variable 來決定是否要 block 某個 thread 等待另一個 thread 完成工作,也可以透過 notify 去讓一個 (或多個) 正在等待 thread 繼續往下執行他們的工作。


要說使用 condition variable 最簡單的架構,應該是 producer/consumer 這種架構了吧。首先 main process fork 一個 thread,然後一個 thread 專門產生資料,另一個 thread 負責把生出來的資料消耗掉。

使用起來的話,下面是個簡單的 example:

vector<int> data;
condition_variable cv;
mutex data_mutex;

bool dumpsd()
{
    unique_lock<mutex> lock(data_mutex);
    cv.wait(lock);
    for (const auto v : data) {  cout << v << '\n';
}

int main()
{
    auto result = async(std::launch::async, dumpsd);
    for(int i = 0; i<10; ++i) { data.emplace_back(i); }
    cv.notify_one();
    // do something else

    result.wait();
    return 0;
}

unique_lock 會把 constructor 拿到的 mutex object 做 lock 確保後續執行時拿到的值不被修改,所以 data_mutex 這個 object 不論你後面程式有沒有要用到在這邊都是必要的

上面這段程式簡單來說的話,就是 main function 會先用 std::async spawn 出一個 thread,負責把 data 中的資料印出來。當然,一開始資料是空的,所以這邊用了 cv.wait 去等候 main thread 把資料填完後用 cv.notify() 通知他可以開始印資料了,而當 cv 呼叫了 notify 後 main thread 自然就可以繼續去做其他事情。

只是事情沒有那麼單純:這個簡單的寫法有一個很大的缺陷在 "當 main thread 先呼叫了 cv.notify() 後,std::async spawn 的 thread 才執行 cv.wait()",這種情況下cv.wait 會等下一次的 cv.notify,此時如果你沒有下一次的 cv.notify,這時候程式就會停在那不動了!所以為了避扁這種情況,最基本的寫法其實會變成下面這樣:

vector<int> data;
condition_variable cv;
mutex data_mutex;
bool flag = false;

bool dumpsd()
{
    unique_lock<mutex> lock(data_mutex);
    cv.wait(lock, []{return (flag == true);});
    for (const auto v : data) {  cout << v << '\n';
}

int main()
{
    auto result = async(std::launch::async, dumpsd);
    for(int i = 0; i<10; ++i) { data.emplace_back(i); }
    flag = true;
    cv.notify_one();
    // do something
    result.wait();
    return 0;
}

well, 其實就是多了一個 flag,用這個 flag 來控制是否要忽略 cv.notify。以這邊的例子來說當 flag = false 時,就算收到了 notify 也要繼續 wait,直到 flag = true 才終止。詳細的說明就參考 cppreference 上的解釋吧

沒有留言:

張貼留言