溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

c++多線程要使用條件變量的原因

發(fā)布時間:2021-06-04 14:35:27 來源:億速云 閱讀:124 作者:小新 欄目:開發(fā)技術

小編給大家分享一下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");
}

結果:

c++多線程要使用條件變量的原因

c++多線程要使用條件變量的原因

可見cpu使用率非常高。高的原因主要在消費者線程中,因為當隊列為空的時候它也要執(zhí)行,做了過多的無用功導致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";
}

結果:

c++多線程要使用條件變量的原因

可見CPU占用率一下子下降了。

這里就有一個困難了,那就是如何確定休眠的時間間隔(即輪詢間隔周期),如果間隔太短會過多占用CPU資源,如果間隔太長會因無法及時響應造成延誤。

這就引入了條件變量來解決該問題:條件變量使用“通知—喚醒”模型,生產者生產出一個數(shù)據(jù)后通知消費者使用,消費者在未接到通知前處于休眠狀態(tài)節(jié)約CPU資源;當消費者收到通知后,趕緊從休眠狀態(tài)被喚醒來處理數(shù)據(jù),使用了事件驅動模型,在保證不誤事兒的情況下盡可能減少無用功降低對資源的消耗。

condition_variable介紹
在C++11中,我們可以使用條件變量(condition_variable)實現(xiàn)多個線程間的同步操作;當條件不滿足時,相關線程被一直阻塞,直到某種條件出現(xiàn),這些線程才會被喚醒。

成員函數(shù)如下:

c++多線程要使用條件變量的原因

條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:

a.一個線程因等待"條件變量的條件成立"而掛起;

b.另外一個線程使"條件成立",給出信號,從而喚醒被等待的線程。

為了防止競爭,條件變量的使用總是和一個互斥鎖結合在一起;通常情況下這個鎖是std::mutex,并且管理這個鎖 只能是 std::unique_lockstd::mutex RAII模板類。

上面提到的兩個步驟,分別是使用以下兩個方法實現(xiàn):

1.等待條件成立使用的是condition_variable類成員wait 、wait_for 或 wait_until。

2.給出信號使用的是condition_variable類成員notify_one或者notify_all函數(shù)。

以上兩個類型的wait函數(shù)都在會阻塞時,自動釋放鎖權限,即調用unique_lock的成員函數(shù)unlock(),以便其他線程能有機會獲得鎖。這就是條件變量只能和unique_lock一起使用的原因,否則當前線程一直占有鎖,線程被阻塞。

虛假喚醒

在正常情況下,wait類型函數(shù)返回時要不是因為被喚醒,要不是因為超時才返回,但是在==實際中發(fā)現(xiàn),因此操作系統(tǒng)的原因,wait類型在不滿足條件時,它也會返回,這就導致了虛假喚醒。==因此,我們一般都是使用帶有謂詞參數(shù)的wait函數(shù),因為這種(xxx, Predicate pred )類型的函數(shù)等價于:

while (!pred()) //while循環(huán),解決了虛假喚醒的問題
{
    wait(lock);
}

原因說明如下:

假設系統(tǒng)不存在虛假喚醒的時,代碼形式如下:

if (不滿足xxx條件)
{
    //沒有虛假喚醒,wait函數(shù)可以一直等待,直到被喚醒或者超時,沒有問題。
    //但實際中卻存在虛假喚醒,導致假設不成立,wait不會繼續(xù)等待,跳出if語句,
    //提前執(zhí)行其他代碼,流程異常
    wait();  
}


//其他代碼
...

正確的使用方式,使用while語句解決:

while (!(xxx條件) )
{
    //虛假喚醒發(fā)生,由于while循環(huán),再次檢查條件是否滿足,
    //否則繼續(xù)等待,解決虛假喚醒
    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");
}

結果:

c++多線程要使用條件變量的原因

還可以將mycv.wait(lcx);換一種寫法,wait()的第二個參數(shù)可以傳入一個函數(shù)表示檢查條件,這里使用lambda函數(shù)最為簡單,如果這個函數(shù)返回的是true,wait()函數(shù)不會阻塞會直接返回,如果這個函數(shù)返回的是false,wait()函數(shù)就會阻塞著等待喚醒,如果被偽喚醒,會繼續(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;

//生產者

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就繼續(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");
}

c++多線程要使用條件變量的原因

以上是“c++多線程要使用條件變量的原因”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

c++
AI