如果熟悉C++多线程的童鞋可能有了解到实现的互斥锁的机制还有这个写法
lock_guard<mutex> guard(mt);
那么这句话是什么意思呢? 为什么又要搞个这样的写法呢?
这个也是构造互斥锁的写法,就是会在lock_guard构造函数里加锁,在析构函数里解锁,之所以搞了这个写法,C++委员会的解释是防止使用mutex加锁解锁的时候,忘记解锁unlock了。
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
using namespace std;
mutex mt;
void thread_task()
{
for (int i = 0; i < 10; i++)
{
lock_guard<mutex> guard(mt);
cout << "print thread: " << i << endl;
}
}
int main()
{
thread t(thread_task);
for (int i = 0; i > -10; i--)
{
lock_guard<mutex> guard(mt);
cout << "print main: " << i << endl;
}
t.join();
return 0;
}
这里说析构函数里解锁,那么到底什么时候调用析构函数呢?构造函数加锁我们好理解,写下这个语句的时候调用lock_guard<mutex> guard(mt),那么调用析构函数应该是大括号{}结束的时候,也就是说定义lock_guard的时候调用构造函数加锁,大括号解锁的时候调用析构函数解锁。
虽然 lock_guard 挺好用的,但是有个很大的缺陷,在定义lock_guard的地方会调用构造函数加锁,在离开定义域的话lock_guard就会被销毁,调用析构函数解锁。这就产生了一个问题,如果这个定义域范围很大的话,那么锁的力度就很大,很大程序上会影响效率。
所以为了解决lock_guard锁的粒度过大的原因,unique_lock就出现了。
unique_lock<mutex> unique(mt);
这个会在构造函数加锁,然后可以利用 unique.unlock() 来解锁,所以当你觉得锁的力度太多的时候,可以利用这个来解锁,而析构的时候会判断当前锁的状态来决定是否解锁,如果当前状态已经是解锁状态了,那么就不会再次解锁,而如果当前状态是加锁状态,就会自动调用 unique.unlock()来解锁。而lock_guard在析构的时候一定会解锁,也没有中途解锁的功能。
当然,方便肯定是有代价的,unique_lock内部会维护一个锁的状态,所以在效率上肯定会比lock_guard慢。
所以,以上两种加锁解锁的方法,加上前面文章介绍的mutex方法,具体该使用哪一个,要依照具体的业务需求来决定,当然mt.lock()和mt.unlock()不管是哪种情况,是肯定都可以使用的。
对我而言,总感觉这个lock_guard有点鸡肋而已,完全可以用mutex来替代,忘记解锁的话一般都可以通过调试发现,而且一般情况下都不会忘记。仅仅只是因为怕忘记解锁这个原因的话,真的感觉有点多此一举,徒增学习成本罢了。
当然也许C++委员会有他们自己的考虑,对于我们而言,也只能记住就是了。