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

C++(11):多线程同步,细闻condition

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

C++(11):多线程同步,细闻condition

condition_variable用于等待信号的函数是wait,wait被重载为两种形式:

  1. void wait (unique_lock& lck);
  2. void wait (unique_lock& lck, Predicate pred);

参数lck是一个unique_lock类型的锁,这个锁通常也是用于保护数据竞争的同一个mutex,如之前的例子:

void handleMsg()
{
	int recvMsg = 0;
	while(recvMsg < MSG_NUM)
	{
		unique_lock locker(msgLock); 
		newMsg.wait(locker);         
		if(msgQueue.size() != 0)
		{
			string& msg = msgQueue.front();
			cout<<"recv "< locker(msgLock); msgLock即是我们用于保护msgQueue写操作的mutex
由于unique_lock构造时即加锁,因此此行语句执行后,msgLocker被加锁

newMsg.wait(locker);会对msgLocker先解锁,以让其他线程可以执行(否则其他线程会被阻塞在这个锁上,那么就没法发送notify,这样会造成死锁),然后wait函数会阻塞当前的线程,并等待其他线程发送condition_variable::notify

其他线程发送condition_variable::notify后,newMsg.wait(locker)停止阻塞,并在返回前先对msgLock加锁,此后线程可以继续执行wait后面的操作,并且由于msgLock处于加锁状态,可以安全的操作竞争数据

数据操作完成后,释放锁:msgLock.unlock();此条语句可以不执行,因为locker会在离开作用域时自动析构,并释放锁

那么带前置条件pred参数(通常是一个函数)的wait有什么作用呢,实际上他可以防止意外的唤醒

首先考虑多个消息处理线程的情况:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

mutex msgLock;
queue msgQueue;
condition_variable newMsg;

const int MSG_NUM = 10;
const string MSG_END = "This is end";

void handleMsg(int id)
{
	bool ifEnd = false;
	vector recvMsg;
	vector noMSg;
	int noMsgNum = 0;
	while(ifEnd == false)
	{
		unique_lock locker(msgLock);
		newMsg.wait(locker);
		if(msgQueue.size() != 0)
		{
			string& msg = msgQueue.front();
			msgQueue.pop();
			msgLock.unlock();    //取出消息后立刻解锁msgLock,以便其他线程可以及时被下一条消息唤醒
			if(msg == MSG_END)   //处理消息
			{
				ifEnd = true;    
			}
			else
			{
				recvMsg.push_back(msg);
			}
		}
		else
		{
			msgLock.unlock();
			noMsgNum++;
			string msg = "no msg " + to_string(noMsgNum);
			noMSg.push_back(msg);
		}
	}
	for(auto &msg : recvMsg)
	{
		cout<<"th"< 

如果有两个消息处理线程th1和th2,当发送消息的线程调用notify_all后(为了提高消息处理的并发性,会唤醒所有阻塞在此条件变量上的线程)两个线程都会被唤醒,但是只有一个能在返回前抢到msgLock锁,所以抢到锁的线程把消息提取出来之后,另外一个线程才能获得msgLock锁,但此时消息队列已经为空了,所以他的唤醒也没什么意义了,只能再次回去wait新的消息,属于无谓的唤醒。

带有前置条件pred参数的wait函数可以很好的处理这种无谓的唤醒,他实际上相当于:

while (!pred()) wait(lck);

当前置条件为false时会一直处于wait阻塞状态,只有当前置条件为true时,才会解除阻塞,也就是说,当解除了wait的阻塞后,前置条件一定是为真的

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

mutex msgLock;
queue msgQueue;
condition_variable newMsg;

const int MSG_NUM = 10;
const string MSG_END = "This is end";

void handleMsg(int id)
{
	bool ifEnd = false;
	vector recvMsg;
	vector noMSg;
	int noMsgNum = 0;
	while(ifEnd == false)
	{
		unique_lock locker(msgLock);
		newMsg.wait(locker, []{return (msgQueue.size() > 0);}); 

        
        //这两种写法是等价的
        //当locker构造获得msgLock锁后,会检查是否有新的消息
        //如果有新消息,因为已经获得了msgLock锁,所以可以直接操作竞争数据,不需要wait了
        //如果没有的话,就进行wait,等待其他线程notify
        //当等到notify后,由于wait返回时获得了msgLock锁,所以可以安全的再次去检查前置条件,如此循环直到前置条件为真
        //也就是说后续的操作可以认为此时前置条件(msgQueue.size() > 0)已经得到了满足
        //因此实际上是不需要下面这个if语句的再次判断,此处为了进行说明,还保留了if
           
		if(msgQueue.size() != 0)
		{
			string& msg = msgQueue.front();
			msgQueue.pop();
			msgLock.unlock();
			if(msg == MSG_END)
			{
				ifEnd = true;
			}
			else
			{
				recvMsg.push_back(msg);
			}
		}
		else
		{
			msgLock.unlock();
			noMsgNum++;
			string msg = "no msg " + to_string(noMsgNum);
			noMSg.push_back(msg);
		}
	}
	for(auto &msg : recvMsg)
	{
		cout<<"th"< 

需要说明的是:

  • condition_variable::notify_one()

        这个函数唤醒某个在wait中阻塞的线程,如果同时存在多个线程阻塞在同一个条件变量上,则         哪个线程被唤醒是不确定的

  • condition_variable::notify_all()

        这个函数会唤醒全部wait阻塞在这个条件变量上的线程

  • condition_variable的拷贝构造函数被删除,因此不能拷贝构造和赋值操作

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

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

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