从lock_guard来说一说C++中常用的RAII

常见问题

在一个函数中(或者一个{...}作用域)有时候会创建/引用了一个资源,而在​这个函数结束的时候需要对这个资源进行释放。常见的场景:

  • 申请了一段内存,退出时候需要释放
  • 打开了一个文件,退出需要关闭文件
  • 统计某个函数调用的当前引用次数,进入的时候引用加一,退出的时候引用减一
  • 某个{...}作用域开始需要加锁,执行完代码后需要解锁
  • 等等…

以上面的锁为例, 在进入函数的时候加锁,在函数退出的时候解锁。
这种写法笔者认为可能会带来两个问题:

  1. 在互斥区的代码有可能会有多处返回return, 在每个return处都加上mutex.unlock()代码感觉显得很不优雅。
  2. 互斥区代码也有可能抛出异常,而有些场景,你并不想在互斥区捕获异常,那么也就不会调用mutext.unlock()从而导致锁并没有释放。
void function()
{
    
    
	mutex.lock();
	//互斥区执行代码;
	//...
	if(...)
	{
    
    
		//...
		mutex.unlock();
		return;
	}
	
	//...
	if(...)
	{
    
    
		//...
		mutex.unlock();
		return;
	}
	//...
	mutex.unlock();
}

RAII正是可以用来解决以上两个问题的,接下来我们来说说RAII

RAII以及lock_guard的实现

笔者是一个朴实的人,发现很多高大上的名字背后,都是些朴实无华的东西。RAII全称为resource acquisition is initialization, 可能现在还无法理解这个含义,等整篇文章读完后再理解下。

RAII主要利用了如下的机制:

  1. 一个对象在其变量作用域结束的时候会调用析构函数
  2. 实现方式: 将资源的获取放在一个对象的构造函数中,然后资源的释放放在这个对象的析构函数。

lock_guard是C++11支持的,不过在此之前boost很早实现,并被广泛使用。然后我们再以第一节的例子,使用lock_guard来实现:

void function()
{
    
    
	std::lock_guard lockGuard(mutex);
	//互斥区执行代码;
	//...
	if(...)
	{
    
    
		//...
		return;
	}
	
	//...
	if(...)
	{
    
    
		//...
		return;
	}
	//...
}

可以看到这个例子,我们做了两件事情,让代码简洁了很多:

  1. mutex作为lockGuard的构造函数参数传递进去
  2. 删除了function函数中所有的mutex.unlock();

那么小伙伴们结合之前的概念可能已经想到了lock_guard的实现。以下是MSVC对lock_guard的实现。那么可以使得:

  1. 在调用lockGuard的构造函数的时候,lockGuard的成员_Mtxmutex进行了引用;并且将调用了_MyMutex.lock();。一句话描述:对象构造函数调用完毕后,则加锁了。此时是不是有点理解resource acquisition is initialization
  2. lockGuard的生命周期就在函数调用结束后return或者函数内抛出异常,则locGuard的析构函数会调用_MyMutex.unlock();从而实现了锁的释放。
		// CLASS TEMPLATE lock_guard
template<class _Mutex>
	class lock_guard
	{
    
    	// class with destructor that unlocks a mutex
public:
	using mutex_type = _Mutex;

	explicit lock_guard(_Mutex& _Mtx)
		: _MyMutex(_Mtx)
		{
    
    	// construct and lock
		_MyMutex.lock();
		}

	lock_guard(_Mutex& _Mtx, adopt_lock_t)
		: _MyMutex(_Mtx)
		{
    
    	// construct but don't lock
		}

	~lock_guard() noexcept
		{
    
    	// unlock
		_MyMutex.unlock();
		}

	lock_guard(const lock_guard&) = delete;
	lock_guard& operator=(const lock_guard&) = delete;
private:
	_Mutex& _MyMutex;
	};

总结

RAII是C++常用的技术,那么我们有必要去理解他,并且利用他:

  1. 使用RAII可以有效的防止资源不及时释放引发的问题: 比如资源泄露,死锁等
  2. RAII的是一种思想,可以拓展到代码的很多场景: 比如从资源池拿到的资源,使用后放回资源池。
  3. RAII中提到的对象的作用域,提醒下一些新手朋友,不一定是指函数。比如你可以在函数内部使用{...}来指定你的作用域(如下所示),灵活的锁定范围。
void function()
{
    
    
	//some code
	//.......
	{
    
    
		RAIIObject robj(object);
		//Do something
	}
	//some code
	//......
}

最后是个人微信公众号,文章CSDN和微信公众号都会发,欢迎一起讨论。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/CJF_iceKing/article/details/115359640
今日推荐