天天看點

多線程協同之 條件變量: condition_variable了解實作例子輸出注意

了解

條件變量(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,但是文章指出會存在不可預測的邊緣效應

推薦第三種寫法。