c++多線程為何要使用條件變量詳解
先看示例1:
#include <iostream> #include <windows.h> #include <mutex> #include<deque> #include <thread> using namespace std; int nmax = 20; std::deque<int> m_que; std::mutex mymutex; //生產者 void producterex() { int i = 1; while (i<nmax) { //休眠一秒鐘 std::this_thread::sleep_for(std::chrono::seconds(1)); std::unique_lock<mutex> lcx(mymutex); m_que.push_back(i); cout << "producted:" << i << endl; lcx.unlock(); i++; } cout << "product thread exit\n"; } //消費者 void consumerex() { int i = 0; while (1) { std::unique_lock<mutex> lcx(mymutex); if (!m_que.empty()) { int i = m_que.back(); m_que.pop_back(); cout << "consumed:" << i << endl; lcx.unlock(); i++; if (i == nmax) { break; } } else { lcx.unlock(); } } cout << "consumerex thread exit\n"; } void main() { std::thread t1(producterex); std::thread t2(consumerex); t1.detach(); cout << "hello"; t2.detach(); cout << " world!\n"; getchar(); system("pause"); }
結果:
可見cpu使用率非常高。高的原因主要在消費者線程中,因為當隊列為空的時候它也要執行,做瞭過多的無用功導致CPU占有率過高,所以下面對進行一個改造讓其在空的時候等待200毫秒,相當於增大瞭輪詢間隔周期,應該能降低CPU的占用率。
在這裡就貼上消費者的線程,因為其它的都一樣。
//消費者 void consumerex() { int i = 0; while (1) { std::unique_lock<mutex> lcx(mymutex); if (!m_que.empty()) { int i = m_que.back(); m_que.pop_back(); cout << "consumed:" << i << endl; lcx.unlock(); i++; if (i == nmax) { break; } } else { lcx.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); } } cout << "consumerex thread exit\n"; }
結果:
可見CPU占用率一下子下降瞭。
這裡就有一個困難瞭,那就是如何確定休眠的時間間隔(即輪詢間隔周期),如果間隔太短會過多占用CPU資源,如果間隔太長會因無法及時響應造成延誤。
這就引入瞭條件變量來解決該問題:條件變量使用“通知—喚醒”模型,生產者生產出一個數據後通知消費者使用,消費者在未接到通知前處於休眠狀態節約CPU資源;當消費者收到通知後,趕緊從休眠狀態被喚醒來處理數據,使用瞭事件驅動模型,在保證不誤事兒的情況下盡可能減少無用功降低對資源的消耗。
condition_variable介紹
在C++11中,我們可以使用條件變量(condition_variable)實現多個線程間的同步操作;當條件不滿足時,相關線程被一直阻塞,直到某種條件出現,這些線程才會被喚醒。
成員函數如下:
條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:
a.一個線程因等待”條件變量的條件成立”而掛起;
b.另外一個線程使”條件成立”,給出信號,從而喚醒被等待的線程。
為瞭防止競爭,條件變量的使用總是和一個互斥鎖結合在一起;通常情況下這個鎖是std::mutex,並且管理這個鎖 隻能是 std::unique_lockstd::mutex RAII模板類。
上面提到的兩個步驟,分別是使用以下兩個方法實現:
1.等待條件成立使用的是condition_variable類成員wait 、wait_for 或 wait_until。
2.給出信號使用的是condition_variable類成員notify_one或者notify_all函數。
以上兩個類型的wait函數都在會阻塞時,自動釋放鎖權限,即調用unique_lock的成員函數unlock(),以便其他線程能有機會獲得鎖。這就是條件變量隻能和unique_lock一起使用的原因,否則當前線程一直占有鎖,線程被阻塞。
虛假喚醒
在正常情況下,wait類型函數返回時要不是因為被喚醒,要不是因為超時才返回,但是在==實際中發現,因此操作系統的原因,wait類型在不滿足條件時,它也會返回,這就導致瞭虛假喚醒。==因此,我們一般都是使用帶有謂詞參數的wait函數,因為這種(xxx, Predicate pred )類型的函數等價於:
while (!pred()) //while循環,解決瞭虛假喚醒的問題 { wait(lock); }
原因說明如下:
假設系統不存在虛假喚醒的時,代碼形式如下:
if (不滿足xxx條件) { //沒有虛假喚醒,wait函數可以一直等待,直到被喚醒或者超時,沒有問題。 //但實際中卻存在虛假喚醒,導致假設不成立,wait不會繼續等待,跳出if語句, //提前執行其他代碼,流程異常 wait(); } //其他代碼 ...
正確的使用方式,使用while語句解決:
while (!(xxx條件) ) { //虛假喚醒發生,由於while循環,再次檢查條件是否滿足, //否則繼續等待,解決虛假喚醒 wait(); } //其他代碼 ....
下面看一個使用條件變量的情況:
#include <iostream> #include <windows.h> #include <mutex> #include<deque> #include <thread> #include<condition_variable> using namespace std; int nmax = 10; std::deque<int> m_que; std::mutex mymutex; condition_variable mycv; //生產者 void producterex() { int i = 1; while (i<nmax) { //休眠一秒鐘 std::this_thread::sleep_for(std::chrono::seconds(1)); std::unique_lock<mutex> lcx(mymutex); m_que.push_back(i); cout << "producted:" << i << endl; lcx.unlock(); mycv.notify_one(); i++; } cout << "product thread exit\n"; } //消費者 bool m_bflag = false; void consumerex() { int i = 0; bool m_bexit = false; while (!m_bexit) { std::unique_lock<mutex> lcx(mymutex); while (m_que.empty()) { //避免虛假喚醒 mycv.wait(lcx); if (m_bflag) { cout << "consumerex thread exit\n"; m_bexit = true; break; } } if (m_bexit) { break; } int i = m_que.back(); m_que.pop_back(); lcx.unlock(); cout << "consumed:" << i << endl; } cout << "consumerex thread exit\n"; } void main() { std::thread t1(producterex); std::thread t2(consumerex); t1.detach(); cout << "hello"; t2.detach(); cout << " world!\n"; mycv.notify_one(); Sleep(15000); m_que.push_back(100); mycv.notify_one(); Sleep(3000); m_bflag = true; mycv.notify_one();//通知線程退出 getchar(); system("pause"); }
結果:
還可以將mycv.wait(lcx);換一種寫法,wait()的第二個參數可以傳入一個函數表示檢查條件,這裡使用lambda函數最為簡單,如果這個函數返回的是true,wait()函數不會阻塞會直接返回,如果這個函數返回的是false,wait()函數就會阻塞著等待喚醒,如果被偽喚醒,會繼續判斷函數返回值。代碼示例如下:
#include <iostream> #include <windows.h> #include <mutex> #include<deque> #include <thread> #include<condition_variable> using namespace std; int nmax = 10; std::deque<int> m_que; std::mutex mymutex; condition_variable mycv; //生產者 void producterex() { int i = 1; while (i<nmax) { //休眠一秒鐘 std::this_thread::sleep_for(std::chrono::seconds(1)); std::unique_lock<mutex> lcx(mymutex); m_que.push_back(i); cout << "producted:" << i << endl; lcx.unlock(); mycv.notify_one(); i++; } cout << "product thread exit\n"; } //消費者 bool m_bflag = false; void consumerex() { int i = 0; while (1) { std::unique_lock<mutex> lcx(mymutex); mycv.wait(lcx, [](){ //返回false就繼續等待 return !m_que.empty(); }); if (m_bflag) { break; } int i = m_que.back(); m_que.pop_back(); lcx.unlock(); cout << "consumed:" << i << endl; } cout << "consumerex thread exit\n"; } void main() { std::thread t1(producterex); std::thread t2(consumerex); t1.detach(); cout << "hello"; t2.detach(); cout << " world!\n"; mycv.notify_one(); Sleep(15000); m_que.push_back(100); mycv.notify_one(); Sleep(3000); m_bflag = true; m_que.push_back(-1); mycv.notify_one();//通知線程退出 getchar(); system("pause"); }
總結
到此這篇關於c++多線程為何要使用條件變量的文章就介紹到這瞭,更多相關c++多線程條件變量內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!