视频教程:
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()就唤醒了个寂寞。