線程(三):條件變量

條件變量的作用

在線程中, 如果有這麼一種情況, 生產者,消費者.
假設用互斥鎖的話, 線程一: 不斷的寫入數據, 線程二 不斷的讀取數據, 那麼線程二基本是一個輪詢, 不斷的加鎖,解鎖,判斷數據. 不斷的輪詢的結果, 浪費性能. 解決辦法, 那就是間隔sleep 一定的時間,但是當條件滿足的時候, 線程二還卡在sleep. 如果實時性高的話, 那就不是很理想.

用到的知識點:

std::mutex
std::unique_lock
std::condition_variable
wait()
notify_one()
_

先進入代碼;後續在詳細說具體出現的情況:

#include<iostream>
#include<condition_variable>
#include<thread>
#include<mutex>
#include<list>
#include<chrono>

std::mutex _mutex;
std::condition_variable condition;
std::list<int>_list;
/*
* 消費者
*  採用 std::unique_lock<std::mutex> lock(_mutex); 加鎖,而不是 lock_guard<mutex> guard(_mutex); why ? 
* 1> 因爲在消費者中, 取出數據, 下面就是對數據進行處理和計算了(線程安全), 這個時候已經不需要在對數據加鎖了, 用 unique_lock 很靈活, 可以進行解鎖, 如果有需要的時候在加鎖,
* 2> 在調用wait 的時候, 當條件不滿足的時候, 會解鎖並阻塞等待, 當notify_one 觸發wait()條件滿足的時候加鎖,(根據條件是否成立, 加鎖解鎖, 這就比lock_guard 靈活的多.)
* 
*/
void wait()
{
    for(;;)
    {
        std::unique_lock<std::mutex> lock(_mutex);				//1
        condition.wait(lock,[]{ return _list.size() !=0;});		//2
        int data = _list.front();
        _list.pop_front();
        std::cout<<"pop data:"<<data<<std::endl;
        lock.unlock();											
        //std::this_thread::sleep_for(std::chrono::milliseconds(20));   //3

    }

}
/*
*生產者
*
*/
void Signal()
{
    for(int i=0;i<1000;i++)
    {
        std::unique_lock<std::mutex> lock(_mutex); //1
        _list.push_back(i);
        std::cout<<"push data:"<<i<<std::endl;
        condition.notify_one();					  //2
    }//3 
}

int main()
{
    std::thread th(wait), th1(Signal);
    th.join();
    th1.join();
    return 0;
}

生產者:

步驟1> 獲得鎖,
步驟2>

notify_one();的作用類似一個觸發條件, 讓阻塞中的wait() 解綁,繼續運行., 運行後進入3 ,解鎖.
這會notify_one 只對阻塞中的 wait 有用, 當消費者處於4步驟中, 或者wait沒有阻塞是不起作用的, 也就是所謂的虛假喚醒

注意:

wait 處於阻塞中:
notify_one 會觸發 讓wait 解阻塞, 並綁定鎖.
wait 非阻塞中:
notify_one 將會是一個虛假喚醒, 這會生產者和消費者 會競爭加鎖, 看誰能搶到鎖, 也有可能消費者處於比較耗時的步驟4, 那麼消費者會搶到鎖,繼續產生數據

消費者

步驟1>: 獲得鎖
步驟2>

第二個參數是一個函數, 會進行判斷, 當條件成立, 返回true, 繼續持有鎖.
如果條件不成立,返回false, : 會解鎖, 阻塞中. 這會發生的事將是:
1:生產者和消費者搶鎖, 生產者搶到,調用notify_one這會就會對wait 產生作用, wait 不再阻塞,並加鎖,往下執行
2: 生產者和消費者搶鎖, 消費者搶到, 又一次判斷條件是否成立,重複上面的步驟.

步驟3>:

當執行到這, 進行耗時的操作的時候, 那就意味着,生產者基本每次都會獲得鎖, 因爲這邊好比延遲. 執行不到std::unique_lock std::mutex lock(mutex) 無法與生產者搶鎖. 繼而生產者每次執行完後調用notify_one 是對 消費者的wait 是無用的(虛假喚醒) ,這就有一個問題, 如下

注意:

這個問題就是, 生產者裏面的數據很多, 消費者消費的很慢. 並不是理論上 生產一個,消費一個 有些人會說用notify_all 多開啓幾個消費者, 加鎖解鎖是線性的, 同時只有一個搶到,其實沒有用的. 至於怎末解決可根據自己的情況這裏不再細講

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章