了解
條件變量(condition_variable) 在cppreference.com 的解釋簡單概括為: 用于阻塞一個或者多個線程,直到另外一個線程喚醒他們。在多線程變成中,可能為多個線程協同完成,在需要多線程同步的場景就可以使用條件變量,舉個例子:
一個程序要實作功能:渲染線程負責從camera擷取圖像,并且渲染;主線程負責控制渲染線程的顯示和隐藏。簡單的實作可能是:是以線程同步的需求就發生了:主線程需要等待子線程完成初始化,才能響應主線程的指令,是以主線程需要等待子線程達到某種條件才行。
- 主線程在需要的時候,建立渲染線程,渲染線程初始化,這個過程可能包括建立surface, EGL的上下文和weston的事件監聽等。
- 主線程負責給渲染線程發指令,顯示和隐藏。子線程接受到信号後顯示和隐藏
實作
具體實作起來需要具備三個因素:
- 通過 std::lock_guard獲得std::mutex鎖。
- 在上鎖的過程,處理業務邏輯
- 使用 notify_one 或者 notify_all 方法喚醒wait在此條件變量的線程。
例子
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex m;
std::condition_variable cv;
std::string data;
bool mainready = false;
bool renderready = false;
void render_thread()
{
// Wait until main() sends data
std::cout << "render_thread begin\n";
std::unique_lock<std::mutex> lk(m);
std::cout << "render_thread wait main thread\n";
// cv.wait(lk);
// cv.wait(lk, []{return mainready; });
cv.wait_for(lk,std::chrono::milliseconds(100), []{return mainready; });
std::cout << "render_thread begin init\n";
/*
init something
*/
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "init something done\n";
renderready = true;
cv.notify_one();
}
int main()
{
std::thread worker(render_thread);
// send data to the worker thread
{
std::cout << "main thread begin to init \n";
std::lock_guard<std::mutex> lk(m);
/* do something in main thread */
mainready = true;
std::cout << "main thread is ready\n";
}
cv.notify_one();
// wait for render thread ready
{
std::unique_lock<std::mutex> lk(m);
std::cout << "main thread begin to wait render thread ready " << '\n';
cv.wait(lk, []{return renderready;});
}
std::cout << "now can send cmd to render thread " << '\n';
worker.join();
}
輸出
render_thread begin
render_thread wait main thread
main thread begin to init
main thread is ready
main thread begin to wait render thread ready
render_thread begin init
init something done
now can send cmd to render thread
注意
主線程跟渲染線程實際上在競賽,那麼主線程和渲染線程的等待情況就可能出現多種。看上面執行個體中的這幾行代碼。
// cv.wait(lk);
// cv.wait(lk, []{return mainready; });
cv.wait_for(lk,std::chrono::milliseconds(100), []{return mainready; });
針對如果主線程的notify的時機早于渲染線程wait的時機的情況:
第一種寫法,那麼單純的wait就可能出現死等的情況。
第二種寫法,測試下來沒有問題,依賴Spurious wakeup,但是文章指出會存在不可預測的邊緣效應
推薦第三種寫法。