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;
//生產(chǎn)者
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";
}
//消費(fèi)者
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");
}
結(jié)果:


可見cpu使用率非常高。高的原因主要在消費(fèi)者線程中,因?yàn)楫?dāng)隊(duì)列為空的時(shí)候它也要執(zhí)行,做了過多的無用功導(dǎo)致CPU占有率過高,所以下面對進(jìn)行一個(gè)改造讓其在空的時(shí)候等待200毫秒,相當(dāng)于增大了輪詢間隔周期,應(yīng)該能降低CPU的占用率。
在這里就貼上消費(fèi)者的線程,因?yàn)槠渌亩家粯印?/p>
//消費(fèi)者
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";
}
結(jié)果:

可見CPU占用率一下子下降了。
這里就有一個(gè)困難了,那就是如何確定休眠的時(shí)間間隔(即輪詢間隔周期),如果間隔太短會(huì)過多占用CPU資源,如果間隔太長會(huì)因無法及時(shí)響應(yīng)造成延誤。
這就引入了條件變量來解決該問題:條件變量使用“通知—喚醒”模型,生產(chǎn)者生產(chǎn)出一個(gè)數(shù)據(jù)后通知消費(fèi)者使用,消費(fèi)者在未接到通知前處于休眠狀態(tài)節(jié)約CPU資源;當(dāng)消費(fèi)者收到通知后,趕緊從休眠狀態(tài)被喚醒來處理數(shù)據(jù),使用了事件驅(qū)動(dòng)模型,在保證不誤事兒的情況下盡可能減少無用功降低對資源的消耗。
condition_variable介紹
在C++11中,我們可以使用條件變量(condition_variable)實(shí)現(xiàn)多個(gè)線程間的同步操作;當(dāng)條件不滿足時(shí),相關(guān)線程被一直阻塞,直到某種條件出現(xiàn),這些線程才會(huì)被喚醒。
成員函數(shù)如下:

條件變量是利用線程間共享的全局變量進(jìn)行同步的一種機(jī)制,主要包括兩個(gè)動(dòng)作:
a.一個(gè)線程因等待"條件變量的條件成立"而掛起;
b.另外一個(gè)線程使"條件成立",給出信號,從而喚醒被等待的線程。
為了防止競爭,條件變量的使用總是和一個(gè)互斥鎖結(jié)合在一起;通常情況下這個(gè)鎖是std::mutex,并且管理這個(gè)鎖 只能是 std::unique_lockstd::mutex RAII模板類。
上面提到的兩個(gè)步驟,分別是使用以下兩個(gè)方法實(shí)現(xiàn):
1.等待條件成立使用的是condition_variable類成員wait 、wait_for 或 wait_until。
2.給出信號使用的是condition_variable類成員notify_one或者notify_all函數(shù)。
以上兩個(gè)類型的wait函數(shù)都在會(huì)阻塞時(shí),自動(dòng)釋放鎖權(quán)限,即調(diào)用unique_lock的成員函數(shù)unlock(),以便其他線程能有機(jī)會(huì)獲得鎖。這就是條件變量只能和unique_lock一起使用的原因,否則當(dāng)前線程一直占有鎖,線程被阻塞。
虛假喚醒
在正常情況下,wait類型函數(shù)返回時(shí)要不是因?yàn)楸粏拘?,要不是因?yàn)槌瑫r(shí)才返回,但是在==實(shí)際中發(fā)現(xiàn),因此操作系統(tǒng)的原因,wait類型在不滿足條件時(shí),它也會(huì)返回,這就導(dǎo)致了虛假喚醒。==因此,我們一般都是使用帶有謂詞參數(shù)的wait函數(shù),因?yàn)檫@種(xxx, Predicate pred )類型的函數(shù)等價(jià)于:
while (!pred()) //while循環(huán),解決了虛假喚醒的問題
{
wait(lock);
}
原因說明如下:
假設(shè)系統(tǒng)不存在虛假喚醒的時(shí),代碼形式如下:
if (不滿足xxx條件)
{
//沒有虛假喚醒,wait函數(shù)可以一直等待,直到被喚醒或者超時(shí),沒有問題。
//但實(shí)際中卻存在虛假喚醒,導(dǎo)致假設(shè)不成立,wait不會(huì)繼續(xù)等待,跳出if語句,
//提前執(zhí)行其他代碼,流程異常
wait();
}
//其他代碼
...
正確的使用方式,使用while語句解決:
while (!(xxx條件) )
{
//虛假喚醒發(fā)生,由于while循環(huán),再次檢查條件是否滿足,
//否則繼續(xù)等待,解決虛假喚醒
wait();
}
//其他代碼
....
下面看一個(gè)使用條件變量的情況:
#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;
//生產(chǎn)者
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";
}
//消費(fèi)者
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");
}
結(jié)果:

還可以將mycv.wait(lcx);換一種寫法,wait()的第二個(gè)參數(shù)可以傳入一個(gè)函數(shù)表示檢查條件,這里使用lambda函數(shù)最為簡單,如果這個(gè)函數(shù)返回的是true,wait()函數(shù)不會(huì)阻塞會(huì)直接返回,如果這個(gè)函數(shù)返回的是false,wait()函數(shù)就會(huì)阻塞著等待喚醒,如果被偽喚醒,會(huì)繼續(xù)判斷函數(shù)返回值。代碼示例如下:
#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;
//生產(chǎn)者
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";
}
//消費(fèi)者
bool m_bflag = false;
void consumerex()
{
int i = 0;
while (1)
{
std::unique_lock<mutex> lcx(mymutex);
mycv.wait(lcx, [](){
//返回false就繼續(xù)等待
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");
}

總結(jié)
到此這篇關(guān)于c++多線程為何要使用條件變量的文章就介紹到這了,更多相關(guān)c++多線程條件變量內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)現(xiàn)獲取本機(jī)MAC地址與IP地址
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)獲取本機(jī)MAC地址與IP地址的兩種方式,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-02-02
詳解如何將Spire.Doc for C++集成到C++程序中
Spire.Doc for C++是一個(gè)專業(yè)的Word庫,供開發(fā)人員在任何類型的C++應(yīng)用程序中閱讀、創(chuàng)建、編輯、比較和轉(zhuǎn)換 Word 文檔,本文演示了如何以兩種不同的方式將 Spire.Doc for C++ 集成到您的 C++ 應(yīng)用程序中,希望對大家有所幫助2023-05-05
總結(jié)UNIX/LINUX下C++程序計(jì)時(shí)的方法
本文總結(jié)了下UNIX/LINUX下C++程序計(jì)時(shí)的一些函數(shù)和方法,對日常使用C++程序的朋友很有幫助,有需要的小伙伴們可以參考學(xué)習(xí),下面一起來看看吧。2016-08-08
關(guān)于C++復(fù)制構(gòu)造函數(shù)的實(shí)現(xiàn)講解
今天小編就為大家分享一篇關(guān)于關(guān)于C++復(fù)制構(gòu)造函數(shù)的實(shí)現(xiàn)講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12
解析c中stdout與stderr容易忽視的一些細(xì)節(jié)
本篇文章是對在c語言中stdout與stderr容易忽視的一些細(xì)節(jié)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
VS2019添加引用出錯(cuò):對COM組件的調(diào)用返回了錯(cuò)誤HRESULT E_FAIL(未能完成操作未指定的錯(cuò)誤)
這篇文章主要介紹了VS2019添加引用出錯(cuò):對COM組件的調(diào)用返回了錯(cuò)誤HRESULT E_FAIL(未能完成操作。未指定的錯(cuò)誤),需要的朋友可以參考下2020-07-07
c語言中實(shí)現(xiàn)數(shù)組幾個(gè)數(shù)求次大值
這篇文章主要介紹了c語言中實(shí)現(xiàn)數(shù)組幾個(gè)數(shù)求次大值,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
Qt實(shí)現(xiàn)抽獎(jiǎng)小游戲的三種方式
本文主要介紹了Qt實(shí)現(xiàn)抽獎(jiǎng)小游戲的三種方式,主要包括while循環(huán),定時(shí)器,線程這三種方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10

