走进C++11(二十六) RAII风格锁std::lock_guard/std::unique_lock

图片

 

今天聊聊std::lock_guard/std::unique_lock,首先要说的是unique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。平时也会使用到std::lock_guard,但是std::unique_lock用的比较少。因为std::lock_guard可以满足我们大部分需求。

 

还有一个关键的问题,我们已经有了std::mutex,为什么还要存在这两个东东呢?因为我们想用RAII风格的锁。首先我们要理解什么是RAII。

 

什么是RAII:

 

资源获取即初始化(Resource Acquisition Is Initialization),或称 RAII,是一种 C++ 编程技术,它将必须在使用前请求的资源(分配的堆内存、执行线程、打开的套接字、打开的文件、锁定的互斥体、磁盘空间、数据库连接等——任何存在受限供给中的事物)的生命周期绑定与一个对象的生存期相绑定。

 

RAII 保证资源可用于任何会访问该对象的函数(资源可用性是一种类不变式,这会消除冗余的运行时测试)。它亦保证所有资源在其控制对象的生存期结束时,以获取顺序的逆序释放。类似地,若资源获取失败(构造函数以异常退出),则为已构造完成的对象和基类子对象所获取的所有资源,会以初始化顺序的逆序释放。这有效地利用了语言特性(对象生存期、退出作用域、初始化顺序以及栈回溯)以消除内存泄漏并保证异常安全。根据 RAII 对象的生存期在退出作用域时结束这一基本状况,此技术的另一名称是作用域界定的资源管理( Scope-Bound Resource Management,SBRM)。

 

RAII 可总结如下:

  • 将每个资源封装入一个类,其中

    • 构造函数请求资源,并建立所有类不变式,或在它无法完成时抛出异常,

    • 析构函数释放资源并决不抛出异常;

  • 始终经由 RAII 类的实例使用满足要求的资源,该资源

    • 自身拥有自动存储期或临时生存期,或

    • 具有与自动或临时对象的生存期绑定的生存期

移动语义使得在对象间,跨作用域,以及在线程内外安全地移动所有权,而同时维护资源安全成为可能。

 

拥有 open()/close()、lock()/unlock(),或 init()/copyFrom()/destroy() 成员函数的类是非 RAII 类的典型的例子:

std::mutex m; void bad() {    m.lock();                    // 请求互斥体    f();                         // 若 f() 抛异常,则互斥体永远不被释放    if(!everything_ok()) return; // 提早返回,互斥体永远不被释放    m.unlock();                  // 若 bad() 抵达此语句,互斥才被释放} void good(){    std::lock_guard<std::mutex> lk(m); // RAII类:互斥体的请求即是初始化    f();                               // 若 f() 抛异常,则释放互斥体    if(!everything_ok()) return;       // 提早返回,互斥体被释放}                                      // 若 good() 正常返回,则释放互斥体

 

了解了他们存在的合理性,我们具体的介绍他们。

 

std::lock_guard

 

  • lock_guard定义于头文件 <mutex>

  • lock_guard是互斥体包装器,为在作用域块期间占有互斥提供便利 RAII 风格机制。

  • 创建 lock_guard 对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。

  • lock_guard 类不可复制。 

 

有了lock_guard,我们就可以写RAII风格的程序了,举个小例子:

 

#include <thread>#include <mutex>#include <iostream> int g_i = 0;std::mutex g_i_mutex;  // 保护 g_i void safe_increment(){    std::lock_guard<std::mutex> lock(g_i_mutex);    ++g_i;     std::cout << std::this_thread::get_id() << ": " << g_i << '\n';     // g_i_mutex 在锁离开作用域时自动释放} int main(){    std::cout << "main: " << g_i << '\n';     std::thread t1(safe_increment);    std::thread t2(safe_increment);     t1.join();    t2.join();     std::cout << "main: " << g_i << '\n';}

 

可能的输出:

main: 0140641306900224: 1140641298507520: 2main: 2

 

std::unique_lock

 

  • 定义于头文件 <mutex>

  • 类unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。

  • 类unique_lock 可移动,但不可复制——它满足可移动构造 (MoveConstructible) 和可移动赋值 (MoveAssignable) 但不满足可复制构造 (CopyConstructible) 或可复制赋值 (CopyAssignable) 。 

  • 类 unique_lock 满足基本可锁定 (BasicLockable) 要求。

  • 若 Mutex 满足可锁定 (Lockable) 要求,则 unique_lock 亦满足可锁定 (Lockable) 要求(例如:能用于 std::lock )

  • 若 Mutex 满足可定时锁定 (TimedLockable) 要求,则 unique_lock 亦满足可定时锁定 (TimedLockable) 要求。 

 

用人话说就是除了lock_guard的功能(也就是:类 unique_lock 满足基本可锁定 (BasicLockable) 要求),unique_lock 还可以锁定两种类型:可锁定 (Lockable),可定时锁定 (TimedLockable)。

 

锁定

lock

锁定关联互斥
(公开成员函数)

try_lock

尝试锁定关联互斥,若互斥不可用则返回
(公开成员函数)

try_lock_for

试图锁定关联的可定时锁定 (TimedLockable) 互斥,若互斥在给定时长中不可用则返回
(公开成员函数)

try_lock_until

尝试锁定关联可定时锁定 (TimedLockable) 互斥,若抵达指定时间点互斥仍不可用则返回
(公开成员函数)

 

所以说unique_lock比lock_guard使用更加灵活,功能更加强大。

 

这里就不举所有的例子了,举一个try_lock的例子:

 

// unique_lock::try_lock example#include <iostream>       // std::cout#include <vector>         // std::vector#include <thread>         // std::thread#include <mutex>          // std::mutex, std::unique_lock, std::defer_lock
std::mutex mtx;           // mutex for critical section
void print_star () {  std::unique_lock<std::mutex> lck(mtx,std::defer_lock);  // print '*' if successfully locked, 'x' otherwise:   if (lck.try_lock())    std::cout << '*';  else                        std::cout << 'x';}
int main (){  std::vector<std::thread> threads;  for (int i=0; i<500; ++i)    threads.emplace_back(print_star);
  for (auto& x: threads) x.join();
  return 0;}

 

可能的输出:

*****************************x******************************x*x***x***x*x*x**x**x**********x********************************************************************************x*x*x*x********************************************************************x********x**********x*******************************************************************************************x*x*x*x**x*x*x*x*x*x***********************x********************************************************************************************x****
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值