问题:使用上一篇讲得双重锁定(双重检查)可以提高效率,避免先创建锁再判断而引起的效率低下问题
但是不断地判断if条件是否满足,也造成了一定的效率问题
解决:
一:条件变量std::conidtion_variable,wait(),notify_one()
解释:有两个线程A和线程B,线程A等待一个条件满足(通知)才继续执行,线程B专门向内存中写入数据,并发布通知
std::condition_variable是一个和条件相关的类,作用:等待一个条件达成(等到通知),需要和互斥量配合使用**
使用的时候生成这个类的对象
继续使用前面的例子进行测试,即包含两个线程(一个写线程和一个读线程)
#include "stdafx.h"
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
//写线程:把收到的消息(玩家命令)放入一个队列
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; i++)
{
cout << "inMsgRecvQueue(),插入一个元素" << i << endl;
std::unique_lock<std::mutex> myUniqueLock(myMutex); //使用unique_lock替代lock_guard
msgRecvQueue.push_back(i); //假设i就是收到的命令,存入消息队列
myCond.notify_one();
//尝试把wait()的线程唤醒,执行完这一行后
//outMsgRecvQueue线程里的wait()就会被唤醒,唤醒之后的事情后续说明.....
}
return;
}
//读线程:把收到的命令取出
void outMsgRecvQueue()
{
int command;
while (true)
{
std::unique_lock<std::mutex> myUniqueLock(myMutex);
//wait()用来等待某一个消息
//如果第二个参数lambda表达式返回值为true,wait()直接返回,程序向下执行
//如果第二个参数的lambda表达式返回值为false,那么wait()将解锁互斥量,并堵塞到本行
//堵塞到其他某个线程调用notify_one()成员函数为止
//如果wait()无第二个参数,即为myCond.wait(myUniqueLock),
//此情况那么就和第二个参数lambda表达式返回false的效果一样
//也就是wait()将解锁互斥量,并堵塞到本行,并堵塞到其他某个线程调用notify_one()成员函数为止
//当其他线程使用notify_one()将此wait()(原来为堵塞状态)唤醒后,wait()恢复执行,如下:
//a)wait()不断尝试重新获取互斥量(锁),如果获取不到,那么流程还是卡在wait()处等着获取互斥量(锁)
//如果获取到锁(加锁),wait()就继续执行下一步b
//b)
//b.1)如果wait有第二个参数lambda表达式,判断lambda表达式返回false
//则wait()又对互斥量解锁,然后又堵塞到此处等待再次被notify_one唤醒
//b.2)如果lambda表达式为true,则wait()返回,流程继续执行(此时互斥锁被锁着)
//b.3)如果wait()没有第二个参数,则wait()返回,流程继续执行
myCond.wait(myUniqueLock,
[this] { //一个lambda表达式就是一个可调用对象(函数)
if (!msgRecvQueue.empty())
return true;
return false;
});
//程序只要能执行到此处,互斥量一定是锁着的,且msgRecvQueue至少有一条数据的
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素,但不返回
myUniqueLock.unlock();
//因为unique_lock的灵活性,提前解锁后(以免锁住太长时间),写线程可以执行了
cout << "outMsgRecvQueue()执行,取出一个命令" << command << endl;
}
}
private:
list<int> msgRecvQueue; //存放玩家发送的命令队列(共享数据)
mutex myMutex; //创建了一个互斥量
std::condition_variable myCond;//创建一个条件变量对象
};
int main(int argc, char** argv)
{
A obj;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &obj); //第二个参数为引用,保证子线程操作的是主线程中的obj
std::thread myInMsgObj(&A::inMsgRecvQueue, &obj); //第二个参数为引用类型
myOutMsgObj.join(); //阻塞主线程并等待子线程执行完毕
myInMsgObj.join();
return 0;
}
上面程序执行时看似没有错误之处,但有不完美的地方:
1.如果写线程inMsgRecvQueue先执行完后,而读线程outMsgRecvQueue还没执行完,则写线程不能执行notify_one通知读线程,读线程就会一直卡在wait()函数处。
2.如果读线程读到数据后,还要执行一系列其他程序,耗时较长,而写线程又再一次执行到notify_one通知读线程,而读线程并没有卡在wait()处,而是在执行其他程序,那么notify_one就可能没有效果
二.notify_all()
notify_one()只能通知一个线程,使得一个线程被唤醒
当我们创建多个读线程outMsgRecvQueue时,如果仍使用notify_one,只能唤醒其中一个线程
所以怎么同时唤醒多个线程呢?使用notify_all()
虽然两个线程都被唤醒了,但是在wait()函数中,两个线程都要尝试获取锁,但是只有一个能获取到锁,未获取到锁的线程还是得卡在wait()函数中
在下面的测试中,再创建一个读线程,使用notify_one和notify_all都是一样的效果,都只有一个线程继续执行
class A
{
public:
//写线程:把收到的消息(玩家命令)放入一个队列
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; i++)
{
cout << "inMsgRecvQueue(),插入一个元素" << i << endl;
std::unique_lock<std::mutex> myUniqueLock(myMutex); //使用unique_lock替代lock_guard
msgRecvQueue.push_back(i); //假设i就是收到的命令,存入消息队列
//myCond.notify_one();
//尝试把wait()的线程唤醒,执行完这一行后,outMsgRecvQueue线程里的wait()就会被唤醒.
myCond.notify_all(); //尝试唤醒所有wait()的线程
}
return;
}
//读线程:把收到的命令取出
void outMsgRecvQueue()
{
int command;
while (true)
{
std::unique_lock<std::mutex> myUniqueLock(myMutex);
//wait()用来等待某一个消息
//如果第二个参数lambda表达式返回值为true,wait()直接返回
//如果第二个参数的lambda表达式返回值为false,那么wait()将解锁互斥量,并堵塞到本行
//堵塞到其他某个线程调用notify_one()成员函数为止
//如果wait()无第二个参数,即myCond.wait(myUniqueLock),那么就第二个参数lambda表达式返回false的效果一样
//也就是wait()将解锁互斥量,并堵塞到本行,并堵塞到其他某个线程调用notify_one()成员函数为止
//当其他线程使用notify_one()将此wait()(原来为堵塞状态)唤醒后,wait()恢复执行
//a)wait()不断尝试重新获取互斥量(锁),如果获取不到,那么流程还是卡在wait()处等着获取互斥量(锁)
//如果获取到锁(加锁),wait()就继续执行下一步b
//b)
//b.1)如果wait有第二个参数lambda表达式,如果判断lambda表达式返回false
//wait()又对互斥量解锁,然后又堵塞到此处等待再次被notify_one唤醒
//b.2)如果lambda表达式为true,则wait()返回,流程继续执行(此时互斥锁被锁着)
//b.3)如果wait()没有第二个参数,则wait()返回,流程继续执行
myCond.wait(myUniqueLock,
[this] { //一个lambda表达式就是一个可调用对象(函数)
if (!msgRecvQueue.empty())
return true;
return false;
});
//程序只要能执行到此处,互斥量一定是锁着的,且msgRecvQueue至少有一条数据的
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素,但不返回
cout << "outMsgRecvQueue()执行,取出一个命令" << command
<< "threadid=" << std::this_thread::get_id() << endl;
myUniqueLock.unlock();
//因为unique_lock的灵活性,提前解锁后(以免锁住太长时间),写线程可以执行了
}
}
private:
list<int> msgRecvQueue; //存放玩家发送的命令队列(共享数据)
mutex myMutex; //创建了一个互斥量
std::condition_variable myCond;//创建一个条件变量对象
};
int main(int argc, char** argv)
{
A obj;
std::thread myOutMsgObj1(&A::outMsgRecvQueue, &obj);
//第二个参数为引用,保证子线程操作的是主线程中的obj
std::thread myOutMsgObj2(&A::outMsgRecvQueue, &obj); //再创建一个读线程
std::thread myInMsgObj(&A::inMsgRecvQueue, &obj); //第二个参数为引用
myOutMsgObj1.join(); //阻塞主线程并等待子线程执行完毕
myOutMsgObj2.join(); //阻塞主线程并等待子线程执行完毕
myInMsgObj.join();
return 0;
}
网友评论