栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

多线程06:条件变量

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程06:条件变量

 优质资源分享 
学习路线指引(点击解锁)知识定位人群定位
李 Python实战微信订餐小程序 李进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
Python量化交易实战入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统
条件变量

与本文无关的知识联系:

一、call_once
  • 函数模板,第一个参数为标记,第二个参数为要调用的函数名,如test()
  • 功能:保证写入第二个参数的函数(如test() )只能被调用一次。具备互斥量的能力,但互斥量消耗的资源少,更高效
  • call_once(), 第一个参数的标记为: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;


本文正题开始:

二、condition_variable
  • 实际上是一个类,是一个和条件相关的类,说白了就是等待一个条件达成!这个类需要和互斥量配合工作,用的时候我们要生成这个类对象,下面都是该类中的方法。
2.1 wait()

先看例子:

std::mutex myMutex;
std::unique_lock uniLock(myMutex);
std::condition_variable cv;
//带两个参数
cv.wait(uniLock, [this] {
    	if (!msgRecvQueue.empty()) return true;
    	return false;
    });
 
//只有一个参数
cv.wait(uniLock);


  • wait()用来等待一个东西,第一个参数是要操作的锁,第二个参数是函数对象(不懂函数对象可以去百度理解,我们后面举例中都采用lambda表达式)
  • 功能:①如果第二个参数的lambda表达式返回值是false,那么wait()将解锁互斥量,并阻塞到本行;②如果第二个参数的lambda表达式返回值是true,那么wait()直接返回并继续执行。
  • 如果没有第二个参数,那么效果跟第二个参数lambda表达式返回false效果一样,wait()将解锁互斥量,并阻塞到本行。
  • 阻塞到其他某个线程调用notify_one()成员函数为止。

当其他线程用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返回,流程走下去。

2.2 wait_for()

std::condition_variable::wait_for的原型有两种:

//第一种不带pre的
template 
  cv_status wait_for (unique_lock& lck,
 const chrono::duration& rel_time);
//第二种,带有pred
template 
       bool wait_for (unique_lock& lck,
 const chrono::duration& rel_time, Predicate pred);


即我们可以写三个参数,我们看看带谓词pre的版本(pre其实也就是wait的第二个参数),wait_for会阻塞其所在线程(该线程应当拥有lock),直至超过了rel_time,或者谓词返回true。在阻塞的同时会自动调用lck.unlock()让出线程控制权。对上述行为进行总结:

  • 只要谓词返回true(阻塞期间只要notify了才会看谓词状态),立刻唤醒线程(返回值为true);
  • 当谓词为false,没超时就继续阻塞,超时了就唤醒(此时返回值为false);
  • 当其他线程使用notify_one或者notify_all进行唤醒时,取决于谓词的状态,若为false,则为虚假唤起,线程依然阻塞。
2.3 notify_one()
  • 只唤醒等待队列中的第一个线程,不存在锁争用(同队列中不存在),所以能够立即获得锁。其余的线程不会被唤醒,需要等待再次调用notify_one()或者notify_all()。
2.4 notify_all()
  • 会唤醒所有等待队列中阻塞的线程,存在锁争用,只有一个线程能够获得锁,而其余的会接着尝试获得锁(类似轮询)

tips: ,java必须在锁内(与wait线程一样的锁)调用notify。但c++是不需要上锁调用的,如果在锁里调用,可能会导致被立刻唤醒的线程继续阻塞(因为锁被notify线程持有)**。c++标准在通知调用中,直接将等待线程从条件变量队列转移到互斥队列,而不唤醒它,来避免此"hurry up and wait"场景。**我在做多线程的题目的时候,notify_one 是在锁内末尾的时候调用的。

代码的优化:

参考一下我的笔记中:拿出数据的函数:

第一种写法:

//拿取数据的函数
bool outMsgPro(int& command) {
    
    {
		std::lock_guard 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 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 myGuard(myMutex);
    //采用wait方式,拿到数据
    cv.wait(myGuard, [this] {
        if (!msgRecvQueue.empty()) return true;
        return false;
    });
    command = msgRecvQueue.front();
    msgRecvQueue.pop();
    myGuard.unlock();
     //其他操作代码
    return;
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/883985.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号