c++线程二

2022/3/21 18:00:40

本文主要是介绍c++线程二,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

接上篇!

其实用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;
}



这篇关于c++线程二的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程