C++11多线程编程(三)——lock_guard和unique_lock

2024-01-10 16:24:24 浏览数 (3)

如果熟悉C 多线程的童鞋可能有了解到实现的互斥锁的机制还有这个写法

代码语言:javascript复制
lock_guard<mutex> guard(mt);

那么这句话是什么意思呢?为什么又要搞个这样的写法呢?

这个也是构造互斥锁的写法,就是会在lock_guard构造函数里加锁,在析构函数里解锁,之所以搞了这个写法,C 委员会的解释是防止使用mutex加锁解锁的时候,忘记解锁unlock了。

代码语言:javascript复制
#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 委员会有他们自己的考虑,对于我们而言,也只能记住就是了。

1 人点赞