与本文无关的知识联系:
std::once_flag
,实际上once_flag是一个结构体,记录函数是否已调用过。例子:
//用once_flag实现单例模式 std::once_flag flag; class Singleton { public: static void CreateInstance()//call_once保证其只被调用一次 { instance = new Singleton; } //两个线程同时执行到这里,其中一个线程要等另外一个线程执行完毕 static Singleton * getInstance() { call_once(flag, CreateInstance); return instance; } private: Singleton() {} static Singleton *instance; }; Singleton * Singleton::instance = NULL;
本文正题开始:
先看例子:
std::mutex myMutex; std::unique_lock<std::mutex> uniLock(myMutex); std::condition_variable cv; //带两个参数 cv.wait(uniLock, [this] { if (!msgRecvQueue.empty()) return true; return false; }); //只有一个参数 cv.wait(uniLock);
当其他线程用notify_one()或者notify_all() 将本线程wait()唤醒后,这个wait恢复后:
1、wait()不断尝试获取互斥量锁,如果获取不到那么流程就卡在wait()这里等待获取继续获取锁,如果获取到了,那么wait()就继续执行2
2.1、如果wait有第二个参数就判断这个lambda表达式。
a)如果表达式为false,那wait又对互斥量解锁,然后又休眠,等待再次被notify_one()唤醒
b)如果lambda表达式为true,则wait返回,流程可以继续执行(此时互斥量已被锁住)。
2.2、如果wait没有第二个参数,则wait返回,流程走下去。
std::condition_variable::wait_for的原型有两种:
//第一种不带pre的 template <class Rep, class Period> cv_status wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time); //第二种,带有pred template <class Rep, class Period, class Predicate> bool wait_for (unique_lock<mutex>& lck, const chrono::duration<Rep,Period>& rel_time, Predicate pred);
即我们可以写三个参数,我们看看带谓词pre的版本(pre其实也就是wait的第二个参数),wait_for会阻塞其所在线程(该线程应当拥有lock),直至超过了rel_time,或者谓词返回true。在阻塞的同时会自动调用lck.unlock()
让出线程控制权。对上述行为进行总结:
notify_one
或者notify_all
进行唤醒时,取决于谓词的状态,若为false,则为虚假唤起,线程依然阻塞。notify_one()
或者notify_all()
。会唤醒所有等待队列中阻塞的线程,存在锁争用,只有一个线程能够获得锁,而其余的会接着尝试获得锁(类似轮询)
tips: ,java必须在锁内(与wait线程一样的锁)调用notify。但c++是不需要上锁调用的,如果在锁里调用,可能会导致被立刻唤醒的线程继续阻塞(因为锁被notify线程持有)。c++标准在通知调用中,直接将等待线程从条件变量队列转移到互斥队列,而不唤醒它,来避免此"hurry up and wait"场景。我在做多线程的题目的时候,notify_one 是在锁内末尾的时候调用的。
代码的优化:
参考一下我的笔记中:拿出数据的函数:
第一种写法:
//拿取数据的函数 bool outMsgPro(int& command) { { std::lock_guard<std::mutex> myGuard(myMutex); if (!msgRecvQueue.empty()) {//非空就进行操作 command = msgRecvQueue.front(); msgRecvQueue.pop(); return true; } } //其他操作代码 return false; }
不管信息接收队列(msgRecvQueue)是不是空,都要加锁解锁,大大降低了效率------这个问题在设计模式中单例模式也有说明
进一步优化:第二种写法(正常优化写法)
//拿取数据的函数 bool outMsgPro(int& command) { //双重锁定 if (!msgRecvQueue.empty()) { std::lock_guard<std::mutex> myGuard(myMutex); if (!msgRecvQueue.empty()) {//非空就进行操作 command = msgRecvQueue.front(); msgRecvQueue.pop(); return true; } } //其他操作代码 return false; }
再进一步优化写法:第三种写法
//拿取数据的函数 std::condition_variable cv; void outMsgPro(int& command) { std::lock_guard<std::mutex> myGuard(myMutex); //采用wait方式,拿到数据 cv.wait(myGuard, [this] { if (!msgRecvQueue.empty()) return true; return false; }); command = msgRecvQueue.front(); msgRecvQueue.pop(); myGuard.unlock(); //其他操作代码 return; }