前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从lock_guard来说一说C++常用的RAII

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

作者头像
河边一枝柳
发布2021-08-06 15:03:43
6690
发布2021-08-06 15:03:43
举报

常见问题

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

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

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

  1. 在互斥区的代码有可能会有多处返回return, 在每个return处都加上mutex.unlock()代码感觉显得很不优雅。
  2. 互斥区代码也有可能抛出异常,而有些场景,你并不想在互斥区捕获异常,那么也就不会调用mutext.unlock()从而导致锁并没有释放。
代码语言:javascript
复制
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来实现:

代码语言:javascript
复制
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();从而实现了锁的释放。
代码语言:javascript
复制
    // 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中提到的对象的作用域,提醒下一些新手朋友,不一定是指函数。比如你可以在函数内部使用{...}来指定你的作用域(如下所示),灵活的锁定范围。
代码语言:javascript
复制
void function()
{
  //some code
  //.......
  {
    RAIIObject robj(object);
    //Do something
  }
  //some code
  //......
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个程序员的修炼之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 常见问题
  • RAII以及lock_guard的实现
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档