c++ 并发与多线程--学习笔记(第8节)

本文讲解了如何使用互斥量和条件变量在多线程环境下提升`inMsgRecvQueue`和`outMsgRecvQueue`的效率,避免消息堆积。通过缓冲区实现批量处理,确保`outMsgRecvQueue`线程能及时响应并处理大量命令。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

视频教程:
https://www.bilibili.com/video/BV1Yb411L7ak?p=10&spm_id_from=pageDriver

上次课程的代码

#include <iostream>
#include <string>
#include <mutex>
#include <condition_variable>
#include<list>
#pragma warning(disable:4996)

using namespace std;

class A
{
public:
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue() //unlock()
	{
		for (int i = 0; i < 10000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

			unique_lock<mutex> sbguard1(my_mutex1);
			msgRecvQueue.push_back(i); //假设这个数字i就是我收到的命令,我直接弄到消息队列里面来;
			//.....
			//其他处理代码
		}
		return;
	}

	bool outMsgLULProc(int& command)
	{
		unique_lock<mutex> sbguard1(my_mutex1);

		if (!msgRecvQueue.empty())
		{
			//消息不为空
			command = msgRecvQueue.front(); //返回第一个元素,但不检查元素是否存在
			msgRecvQueue.pop_front();  //移除第一个元素,但不返回
			return true;
		}
		return false;
	}

	//把数据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;
		for (int i = 0; i < 10000; ++i)
		{
			bool result = outMsgLULProc(command);
			if (result == true)
			{
				cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
				//可以考虑进行命令(数据)处理
				//...
			}
			else
			{
				//消息队列为空
				cout << "outMsgRecvQueue()执行,但目前消息队列为空" << i << endl;
			}
		}
	}

private:
	list<int> msgRecvQueue; //容器(消息队列),专门用于代表玩家给咱们发送过来的命令
	mutex my_mutex1; //创建了一个互斥量(一把锁头)
};

int main()
{
	A myobja;
	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //第二个参数是引用,才能保证线程里用的是同一个对象
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	myOutMsgObj.join();
	myInMsgObj.join();

	return 0;
}

condition_variable 示例

#include <iostream>
#include <string>
#include <mutex>
#include <condition_variable>
#include<list>
#pragma warning(disable:4996)

using namespace std;

class A
{
public:
	//把收到的消息(玩家命令)入到一个队列的线程
	void inMsgRecvQueue() //unlock()
	{
		for (int i = 0; i < 10000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;

			unique_lock<mutex> sbguard1(my_mutex1);
			msgRecvQueue.push_back(i); //假设这个数字i就是我收到的命令,我直接弄到消息队列里面来;
			my_cond.notify_one(); //尝试把wait()的线程唤醒,执行完这行,那么outMsgRecvQueue()里边的wait就会被唤醒
			//.....
			//其他处理代码
		}
		return;
	}

	//把数据从消息队列中取出的线程
	void outMsgRecvQueue()
	{
		int command = 0;

		while (true)
		{
			unique_lock<mutex> sbgurad1(my_mutex1);

			//wait()用来等一个东西
			//如果返回值是true,代码继续往下走
			//如果wait没有第二个参数,那么就跟第二个参数lambda表达式返回false效果一样,直到其他线程调用notify_one()
				//wait()将解锁互斥量,并堵塞本行,堵塞到其他某个线程调用notify_one()成员函数为止
				//my_cond.wait(sbgurad1);
			//如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量,并阻塞到本行
				//那么阻塞到什么时候为止呢?堵塞到其他某个线程调用notify_one()成员函数为止
			//当其他线程用notify_one()将本wait(原来是睡着(阻塞)状态唤醒后,wait就开始干活了,恢复后wait干什么活呢?
				//a) wait()不断地尝试重新获取互斥量锁,如果获取不到,那么流程就卡在wait这里等着获取,如果获取到了锁(等于加了锁),那么wait就继续执行b;
				//b)
						//b.1)如果wait有第二个参数(lambda),就判断这个lambda表达式,如果lambda表达式为false,那wait又对互斥量解锁
						//b.2)如果lambda表达式为true,则wait返回,流程走下来(此时互斥锁被锁着)
						//b.3)如果wait没有第二个参数,则wait返回,流程走下来
			my_cond.wait(sbgurad1, [this] { //一个lambda表达式就是一个可调用对象(函数)
				if (!msgRecvQueue.empty())
					return true;
				return false;
				});

			//流程只要能走到这里来,这个互斥锁一定是锁着的

			command = msgRecvQueue.front(); //返回第一个元素,但不检查元素是否存在
			msgRecvQueue.pop_front();  //移除第一个元素,但不返回
			sbgurad1.unlock(); //因为unique_lock的灵活性,所以我们可以随时地unloc,以免锁住太长时间
			cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
		}

	}

private:
	list<int> msgRecvQueue; //容器(消息队列),专门用于代表玩家给咱们发送过来的命令
	mutex my_mutex1; //创建了一个互斥量(一把锁头)
	condition_variable my_cond; //生成一个条件变量对象
};

int main()
{
	A myobja;
	thread myOutMsgObj(&A::outMsgRecvQueue, &myobja); //第二个参数是引用,才能保证线程里用的是同一个对象
	thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
	myOutMsgObj.join();
	myInMsgObj.join();

	return 0;
}

深入思考:
上面的代码并不够完善:
因为outMsgRecvQueue()与inMsgRecvQueue()并不是一对一执行的,所以当程序循环执行很多次以后,可能在msgRecvQueue 中已经有了很多消息,但是,outMsgRecvQueue一次只处理一条数据。
可以加上一个缓冲区,一次处理多条数据。

此外,outMsgRecvQueue()要比inMsgRecvQueue()的线程先开始执行,所以在主函数中先创建outMsgRecvQueue()线程的函数。如果是inMsgRecvQueue()先开始执行,此时outMsgRecvQueue()没有执行,wait()没运行,没有将处理数据的线程卡在那里,inMsgRecvQueue()中的notice_one()就唤醒了个寂寞。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值