ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

c++线程二

2022-03-21 15:33:23  阅读:173  来源: 互联网

标签:std lock c++ try mutex 线程 unique


接上篇!

其实用mutex的lock()、unlock(), 当然更好用lock_guard(),这些基本能满足我们的应用需求,且容易理解。

一、unique_lock 相比lock_gaurd有一些更灵活的用法

主要体现在unique_lock的参数和成员函数上。如下一目了然,不再举例子。

unique_lock<mutex> uniqeLock(mu, std::adopt_lock);//同lock_guard, 指示不加锁,前提是mutex已经加锁
unique_lock<mutex> uniqeLock(mu, std::try_to_lock);//尝试加锁,用在获取不到锁时执行一些其他操作
unique_lock<mutex> uniqeLock(mu, std::defer_lock); //不加锁,且mutex尚未锁定,配合成员函数使用
uniqeLock.try_lock();  //同参数std::try_to_lock
uniqeLock.owns_lock(); //返回bool,表明是否拥有锁
uniqeLock.release();   //返回mutex,解除unique_lock与mutex的绑定,unique_lock为空

其次,我们常用的用法是unique_lock的控制粒度很细。

unique_lock在析构时会自动判断是否需要解锁:

unique_lock<mutex> ulock(mu);
//执行一些操作
ulock.unlock();  //我们可以随时unlock

ulock.lock();
//执行另外的操作

//不用unlock,会自动判断

二、以上的lock_guard、unique_lock用法都是基于mutex,

除此之外还有std::recursive_mutex和std::timed_mutex

1、recursive_mutex:递归互斥锁,相对于mutex的互斥锁

mutex myMutex;
void fun1()
{
	myMutex.lock();
	fun2();             //同样fun2中也需要锁,这种情况mutex显然不能胜任
	//线程1操作....
	myMutex.unlock();
}
void fun2()
{
	myMutex.lock();
	//线程2操作....
	myMutex.unlock();
}

int main()
{
	myMutex.lock();
	fun1();           //调用fun1,但mutex只能lock一次
	myMutex.unlock();

	return 0;
}

这种情况,只能用recursive_lock,能递归加锁,用于这种在同一线程嵌套调用加锁的情况。

即同一线程内lock或try_to_lock成功后开始占有锁,并可以多次lock,直到匹配到同样多的unlock后释放锁。早占有期间,其他线程lock会阻塞!

当然同mutex一样,最好是配合lock_guard和unique_lock使用。

2、std::timed_mutex 在一定时期内尝试获取锁,获取到锁就返回。

1、try_lock_for类似与unique_lock<mutx> uLock(mu, std::try_to_lock);

void fun2()
{
	while (true)
	{
		if (tMutex.try_lock_for(chrono::seconds(1)))
		{
			cout << "fun2 开始执行" << endl;
			tMutex.unlock();
		}
		else
		{
			cout << "fun2 没获取锁" << endl;
		}
	}
}

2、try_lock_until()

tMutex.try_lock_until(chrono::steady_clock::now()+10s)

3、当然同mutex,超时锁也有递归版本 :recursive_timed_mutex

二、条件变量condition_variable

互斥锁是用来防止竞争问题,条件变量是解决“线程同步”问题。一个线程处理完后,通知另外的线程处理。

1、wait()、  notify_one()、notify_all()

对wait()的使用,详见代码的注释,因为只要通知一个线程,所以用notify_one()通知。

class A
{
public:
	void inQueueMsg()
	{	
		for (int i = 0; i < 100000; i++)
		{
			unique_lock<mutex> uniqueLock(mu);
			cout << "线程id:" << this_thread::get_id() << " 插入数据" << endl;
			msgQueue.push(i);
			condv.notify_one();
		} 
	}
	void outQueueMsg()
	{
		for (int i = 0; i < 100000; i++)
		{
			unique_lock<mutex> uniqLock(mu);
			condv.wait(uniqLock, [this]() {  //wait有两个重载 分别为1个参数和两个参数,都必须用unique_lock
				if (msgQueue.empty())        //wait会阻塞线程,直到收到notify信号,此时的阻塞会释放拿到的锁,并进入睡眠
					return false;            //收到notify信号,会再次尝试获取锁,获取后若没有第二个参数则走下去,
				return true;                 //若有第二个参数,则判断,为真走下去,为假则再次释放锁,进入睡眠
			});
			//走到此处说明非空,不用判断
			int m = msgQueue.front();
			msgQueue.pop();
			cout << "outQueueMsg线程id:" << this_thread::get_id << " 读出数据:" << m << endl;
		}
	}
private:
	queue<int> msgQueue;
	mutex mu;
	condition_variable condv;
};

int main()
{
	A ac;
	thread t1(&A::outQueueMsg, &ac);
	thread t2(&A::inQueueMsg, &ac);
	t1.join();
	t2.join();
	
	return 0;
}

notify_one()只会唤醒一个wait的线程,此线程尝试去拿锁,拿不到则一直尝试拿;

nofity_all()唤醒所有wait的线程,所有线程都会去拿锁,但只有一个拿到,所以会“惊群”,其他拿不到锁的都会一直尝试拿锁!

另外,如果发送notify时,其他线程没在wait上阻塞,则此次notify不会产生任何作用(notify信号不会被储存,而是直接消失)。

三、原子操作

因为这种互斥的情况,c++标准增加原子操作,std::atomic<>类模板,但只能是针对内置普通变量,所以一般只用在计数上。

std::atomic<int> g_count = 0;
void func()
{
	for (int i = 0; i < 100000; i++)
		g_count++;   //不需互斥,因为本身是原子操作,执行时cpu不会调度
}
int main()
{
	thread t1(func);
	thread t2(func); //多个线程对g_count进行操作
	t1.join();
	t2.join();
	cout << "g_count=" << g_count << endl;
	
	return 0;
}

标签:std,lock,c++,try,mutex,线程,unique
来源: https://blog.csdn.net/lichao201005/article/details/123629798

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有