纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

c++多线程条件变量 c++多线程为何要使用条件变量详解

发如雪-ty   2021-06-04 我要评论
想了解c++多线程为何要使用条件变量详解的相关内容吗,发如雪-ty在本文为您仔细讲解c++多线程条件变量的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:c++线程类,c++多线程,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");
}

总结


相关文章

猜您喜欢

  • mysql返回Boolean类型 浅谈mysql返回Boolean类型的几种情况

    想了解浅谈mysql返回Boolean类型的几种情况的相关内容吗,海棠xun在本文为您仔细讲解mysql返回Boolean类型的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:mysql,返回Boolean类型,类型返回,下面大家一起来学习吧。..
  • opencv读取视频 opencv读取视频并保存图像的方法

    想了解opencv读取视频并保存图像的方法的相关内容吗,zhouxuechao在本文为您仔细讲解opencv读取视频的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:opencv读取视频,opencv读取视频保存,下面大家一起来学习吧。..

网友评论

Copyright 2020 www.moon-script.com 【月光下载】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式