并发与多线程笔记8

1 先看一下以下示例代码,是不是存在一定风险呢?

#include <iostream>
#include <vector>
#include <thread>
#include <mutex>
#include <list>

using namespace std;

class M
{
public:
	void InmessageQueue()
	{
		cout << "将要存数据,还没拿到锁,这个线程id是: " << std::this_thread::get_id() << endl;
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard(my_mutex);
			cout << "插入一个元素。" << endl;
			messageQueue.push_back(i);
		}
	}
	void OutputMessage()
	{
		int command;
		while (1)//注意,这里不会判断数据队列里是否为空,就是一味的取数据。
		{
			std::unique_lock<std::mutex> sguard(my_mutex);
			
			cout << "正在取数据,这个线程id是: " << std::this_thread::get_id() << endl;
			command = messageQueue.front();
			messageQueue.pop_front();
		}
		cout << "end......." << endl;
	}

private:
  std::list<int> messageQueue;
	std::mutex my_mutex;
	
};


int main()
{
	// 一、条件变量std::condition_variable\wait()\notify_one()
		//线程A: 等待一个条件满足
		//线程B: 专门往消息队列中扔数据
	M queue;
	thread mytobj2(&M::OutputMessage, &queue);
	thread mytobj1(&M::InmessageQueue, &queue);

	mytobj1.join();
	mytobj2.join();

	return 0;
}

上述代码,我们看到取数据线程是不会判断数据队列中是否有数据的,并且我们启动线程时,取数据线程首先启动,这就很容易导致,取数据线程先拿到锁,当队列里没有数据时,程序就会崩溃。运行结果如下:
在这里插入图片描述
条件变量可以解决这个问题。

2 条件变量std::condition_variable\wait()\notify_one()

条件变量实现下列功能:
线程A: 等待一个条件满足
线程B: 专门往消息队列中扔数据
std::condition_variable是一个类,说白了就是等待一个条件达成。
得和互斥量配合工作

联想到上面那段代码,我们可以提出这样的解决方案,在取数据的线程中设置等待,要等到存数据线程给“已经插入数据”的信号以后再进行取数据。
那他怎么实现的呢?

①在类中声明一个条件变量对象:std::condition_variable mycond;
②在取数据线程设置等待mycond.wait(sguard);
③同时在存数据线程中设置唤醒mycond.notify_one();

private:
	std::list<int> msgRecvQueue;// 容器,专门用于代表晚间给咱们发送过来的指令。
	std::mutex my_mutex;// 创建一个互斥量(一把锁)
	std::condition_variable mycond;// 条件变量对象,需要和互斥量配合使用
void Outmessage()
{
	int command;
	while (1)
	{
		std::unique_lock<std::mutex> sguard(my_mutex);
		/*如果lamada表达式返回false,那就解锁互斥量并堵塞到本行,注意这块已经解锁了哈。
		 直到存数据线程调用了notify_one()。
		 mycond.wait(sguard),跟上面一样。*/
		mycond.wait(sguard, [this] {
			if (!msgRecvQueue.empty())
			{
				return true;
			}
			else
				return false;
		
		});
		 /*等被notify_one唤醒后,注意
		 a)wait不断尝试重新去拿锁,如果获取不到,仍会卡在此位置。(有可能存数据线程唤醒了他,但是存数据线程还拿着锁呢)
		 b)拿到锁,1)若wait有第二个参数,那么就判断第二个参数的bool值,又变为上面的问题了。
				   2)若没有第二个参数,那么就相当于第二个参数返回true了。
		*/
		cout << "正在取数据,这个线程id是: " << std::this_thread::get_id() << endl;
		command = messageQueue.front();

		messageQueue.pop_front();

		sguard.unlock();//unique_lock的灵活性,可以随时解锁。

	}
	cout << "end......." << endl;
}
void InmessageQueue()
	{
		cout << "将要存数据,还没拿到锁,这个线程id是: " << std::this_thread::get_id() << endl;
		for (int i = 0; i < 10000; i++)
		{
			std::unique_lock<std::mutex> sbguard(my_mutex);

			cout << "插入一个元素。id 为 " << std::this_thread::get_id() << endl;
			messageQueue.push_back(i);

			mycond.notify_one();//尝试唤醒取数据线程
		}
	}

3 notify_all()

创建两个取数据的线程,要想在插数据的线程中把所有取数据线程唤醒,就用这个。了解了notify_one(),也就很容易理解notify_all(),就是尝试唤醒多个线程,比如,我们创建两个取数据线程,那我们就得在存数据线程中用notify_all()来唤醒其中一个线程(随机唤醒,由于互斥锁的存在仍是只有一个线程取数据)。

M queue;
thread myOutnMsgObj(&M::OutputMessage, &queue);//注意,第二个对象要用引用,保证子线程中用的就是主线程提供的对象。
thread myOutnMsgObj1(&M::OutputMessage, &queue);

thread myInMsgObj(&M::InmessageQueue, &queue);


myOutnMsgObj.join();
myOutnMsgObj1.join();
myInMsgObj.join();

void InmessageQueue()
{
	for (int i = 0; i < 10000; i++)
	{
		cout << "inMsgRecvQueue()执行,插入一个元素 " << i << endl;

		std::unique_lock<std::mutex> sbguard(my_mutex);//将unique_lock和mutex绑定,并lock()
	
		messageQueue.push_back(i);// 把收到的消息,放进消息队列中。
		mycond.notify_all();//换成notify_all()
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宛如新生

转发即鼓励,打赏价更高!哈哈。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值