C++併發實戰11:條件變數
阿新 • • 發佈:2019-02-15
1 執行緒睡眠函式
std::this_thread::sleep_for(std::chrono::milliseconds(100));//標頭檔案#include<chrono>,供選擇的如seconds()等
不要使用睡眠函式同步執行緒,睡眠函式可以用於復現執行緒的一些行為。
執行緒重新排程函式:
std::this_thread::yield()
重新排程本執行緒,用於執行緒等待其它執行緒時而不阻塞本執行緒2 條件變數std::condition_variable不允許拷貝和移動的。其基本語義和Linux的pthread_cond_t差不多。
condition_variable(); condition_variable (const condition_variable&) = delete;//沒有copy constructor和move constructor ~condition_variable();//阻塞在此條件變數上的執行緒將被喚醒,不會有執行緒再對此條件變數wait void wait (unique_lock<mutex>& lck);//等待條件發生,注意引數是unique_lock,可能發生虛假喚醒,即不是notify喚醒的 template <class Predicate> void wait (unique_lock<mutex>& lck, Predicate pred);//pred是個函式物件返回bool,執行緒會在pred返回false的下阻塞,pred返回true會被喚醒,這樣可以防止虛假喚醒等價於:while (!pred()) wait(lck); template <class Rep, class Period> cv_status wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time);//在指定rel_time時間段內等待,此期間可能被notify喚醒 template <class Rep, class Period, class Predicate> bool wait_for (unique_lock<mutex>& lck,const chrono::duration<Rep,Period>& rel_time, Predicate pred);//為了防止虛假喚醒,加了一個pred,在rel_time內被notify且要pred返回true方可被喚醒 template <class Clock, class Duration> cv_status wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time);//等待到一個指定的絕對時間點,語義和wait_for差不多 template <class Clock, class Duration, class Predicate> bool wait_until (unique_lock<mutex>& lck,const chrono::time_point<Clock,Duration>& abs_time,Predicate pred); void notify_one() noexcept;//若有多個執行緒等待此條件變數則選擇一個執行緒喚醒,若沒有執行緒等待則此函式什麼也不做 void notify_all() noexcept;//喚醒所有阻塞在此條件變數上的執行緒,若沒有阻塞執行緒此函式什麼也不做
3 下面是一個使用condition_variable和queue實現的單生產者單消費者程式碼片段
##1##處說明:std::mutex mut;//該mutex會被condition_variable使用 std::queue<data_chunk> data_queue;//用於在生產者執行緒和消費者執行緒間傳遞資料 std::condition_variable data_cond; void data_preparation_thread()//生產者 { while(more_data_to_prepare()) { data_chunk const data=prepare_data(); std::lock_guard<std::mutex> lk(mut);//這裡可以用lock_guard或者unique_lock data_queue.push(data);// data_cond.notify_one();//喚醒一個阻塞在條件變數的執行緒,多個阻塞執行緒會選擇一個喚醒,若沒有阻塞的執行緒則什麼也不做 } } void data_processing_thread()//消費者 { while(true)//不斷消費 { std::unique_lock<std::mutex> lk(mut);//注意condition_varibale必須使用unique_lock,因為unique_lock提供了lock和unlock操作更加靈活 data_cond.wait(lk,[]{return !data_queue.empty();});//##1##lambda表示式用於檢測queue是否為空,這裡可以是任意的callable object data_chunk data=data_queue.front(); data_queue.pop(); lk.unlock();//wait返回時mutex處於locked,為了提高併發應該立即顯示解鎖 process(data);//處理資料 if(is_last_chunk(data)) break; } }
wait首先檢查lambda表示是否為真(queue是否為空),若為假(queue中有資料)wait直接返回;若為假則表明條件不滿足,那麼wait會將mutex解鎖,並使執行緒進入阻塞狀態,這裡看出為什麼要用unique_lock了吧因為其提供了比lock_guard更靈活的lock和unlock操作。
當阻塞的執行緒被notify_one()/notify_all()喚醒時,首先對mutex上鎖,並檢查lambda表示式(queue是否為空的條件)。若條件滿足(queue非空)則wait立即返回,mutex處於locked。若條件仍不滿足(queue為空),wait將對mutex解鎖,並繼續阻塞執行緒在waiting狀態。
4 測試wait前後mutex的狀態
#include<iostream>
#include<thread>
#include<mutex>
#include<chrono>
#include<condition_variable>
using namespace std;
mutex m;
condition_variable cond;
int flag=0;
void producer(){
this_thread::sleep_for(chrono::seconds(1));
lock_guard<mutex> guard(m);
flag=100;
cond.notify_one();
cout<<"notify..."<<endl;
}
void customer(){
unique_lock<mutex> lk(m);
if(m.try_lock())
cout<<"mutex unlocked after unique_lock"<<endl;
else
cout<<"mutex locked after unique_lock"<<endl;//輸出
while(flag==0){
cout<<"wait..."<<endl;
cond.wait(lk);
}
if(m.try_lock())
cout<<"mutex unlocked after wait"<<endl;
else
cout<<"mutex locked after wait"<<endl;//輸出
cout<<"flag==100? "<<flag<<endl;
}
int main(){
thread one(producer);
thread two(customer);
one.join();
two.join();
return 0;
}
程式輸出:
mutex locked after unique_lock //unique_lock(mutex&)會擁有mutex並對mutex上鎖
wait...
notify...
mutex locked after wait //wait返回後mutex仍處於locked狀態
flag==100? 100