什麼是條件變量?
條件變量是一種同步機制,允許線程挂起,直到共享資料上的某些條件得到滿足。條件變量上的基本操作有:觸發條件(當條件變為 true 時);等待條件,挂起線程直到其他線程觸發條件。條件變量要和互斥量相聯結,以避免出現條件競争--一個線程預備等待一個條件變量,當它在真正進入等待之前,另一個線程恰好觸發了該條件。
什麼意思呢?不清楚沒關系。看了例子就知道了:問題描述:假設有一個bool型全局變量 isTrue ,現有10個線程,線程流程如下:當isTrue為真時,doSomething;否則挂起線程,直到條件滿足。那麼,用thread和mutex如何實作這個功能呢?
#include <vector>
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;
bool isTrue = false;
void doSomething()
{
cout << "this is : " << this_thread::get_id() << endl;
}
void thread_Func()
{
while (!isTrue)
this_thread::yield();
doSomething();
}
int main()
{
vector<thread> v;
v.reserve(10);
for (int i = 0; i < 10; i++)
{
v.emplace_back(thread_Func);
}
this_thread::sleep_for(chrono::seconds(2));
isTrue = true;
for (auto& t : v)
{
t.join();
}
return 1;
}
這段代碼雖然能滿足需求,但有一個大缺點,就是當條件為假時,子線程會不停的測試條件,這樣會消耗系統資源。我們的思想是,當條件為假時,子線程挂起,直到條件為真時,才喚醒子線程。
nice,條件變量就是幹這事的!
先來看看條件變量的介紹:
條件變量能夠挂起調用線程,直到接到通知才會喚醒線程。它使用unique_lock<Mutex>來配合完成。下面是用條件變量實作需求:
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <chrono>
#include <condition_variable>
using namespace std;
bool isTrue = false;
std::mutex mtx;
std::condition_variable cv;
void doSomething()
{
cout << "this is : " << this_thread::get_id() << endl;
}
void thread_Func()
{
unique_lock<mutex> loc(mtx);
while (!isTrue)
cv.wait(loc);
doSomething();
}
int main()
{
vector<thread> v;
v.reserve(10);
for (int i = 0; i < 10; i++)
{
v.emplace_back(thread_Func);
}
this_thread::sleep_for(chrono::seconds(2));
{
unique_lock<mutex> loc(mtx);
isTrue = true;
cv.notify_all();
}
for (auto& t : v)
{
t.join();
}
return 1;
}
我們發現,在條件變量cv的wait函數中,我們傳入了一個lock參數,為什麼要用鎖呢?因為isTrue為臨界變量,主線程中會“寫”它,子線程中要“讀”它,這樣就産生了資料競争,并且這個競争很危險。
我們把while (!isTrue) cv.wait(loc)拆開:
1、條件判斷
2、挂起線程
假如現在1執行完後,時間片輪轉,該子線程暫停執行。而恰好在這時,主線程修改了條件,并調用了cv.notify_all()函數。這種情況下,該子線程是收不到通知的,因為它還沒挂起。等下一次排程子線程時,子線程接着執行2将自己挂起。但現在主線程中的notify早已經調用過了,不會再調第二次了,是以該子線程永遠也無法喚醒了。
為了解決上面的情況,就要使用某種同步手段來給線程加鎖。而c++11的condition_variable選擇了用unique_lock<Mutex>來配合完成這個功能。并且我們隻需要加鎖,條件變量在挂起線程時,會調用原子操作來解鎖。
c++11還為我們提供了一個更友善的接口,連同條件判斷,一起放在條件變量裡。上面的線程函數可做如下修改:
void thread_Func()
{
unique_lock<mutex> loc(mtx);
cv.wait(loc, []() -> bool { return isTrue;} );
doSomething();
}