c++多线程要使用条件变量的原因

发布时间:2021-06-04 14:35:27 作者:小新
来源:亿速云 阅读:135

小编给大家分享一下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使用率非常高。高的原因主要在消费者线程中,因为当队列为空的时候它也要执行,做了过多的无用功导致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资源,如果间隔太长会因无法及时响应造成延误。

这就引入了条件变量来解决该问题:条件变量使用“通知—唤醒”模型,生产者生产出一个数据后通知消费者使用,消费者在未接到通知前处于休眠状态节约CPU资源;当消费者收到通知后,赶紧从休眠状态被唤醒来处理数据,使用了事件驱动模型,在保证不误事儿的情况下尽可能减少无用功降低对资源的消耗。

condition_variable介绍
在C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作;当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。

成员函数如下:

c++多线程要使用条件变量的原因

条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:

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");
}

结果:

c++多线程要使用条件变量的原因

还可以将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++多线程要使用条件变量的原因”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

推荐阅读:
  1. mysql要索引的原因
  2. python要缩进控制的原因

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

c++

上一篇:python中opencv通过4坐标剪裁图片的示例分析

下一篇:怎么用vue封装axios请求

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》