简介

std::lock_guard 简介
这个类是一个互斥量的包装类,用来提供自动为互斥量上锁和解锁的功能,简化了多线程编程,用法如下:

  1. #include <mutex>
  2. std::mutex kMutex;
  3. void function() {
  4. // 构造时自动加锁
  5. std::lock_guard<std::mutex> (kMutex);
  6. // 离开局部作用域,析构函数自动完成解锁功能
  7. }

描述

互斥类的最重要成员函数是lock()和unlock()。在进入临界区时,执行lock()加锁操作,如果这时已经被其它线程锁住,则当前线程在此排队等待。退出临界区时,执行unlock()解锁操作。更好的办法是采用”资源分配时初始化”(RAII)方法来加锁、解锁,这避免了在临界区中因为抛出异常或return等操作导致没有解锁就退出的问题。极大地简化了程序员编写mutex相关的异常处理代码。C++11的标准库中提供了std::lock_guard类模板做mutex的RAII。

std::lock_guard类的构造函数禁用拷贝构造,且禁用移动构造。std::lock_guard类除了构造函数和析构函数外没有其它成员函数。

在std::lock_guard对象构造时,传入的mutex对象(即它所管理的mutex对象)会被当前线程锁住。在lock_guard对象被析构时,它所管理的mutex对象会自动解锁,不需要程序员手动调用lock和unlock对mutex进行上锁和解锁操作。lock_guard对象并不负责管理mutex对象的生命周期,lock_guard对象只是简化了mutex对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个lock_guard对象的生命周期内,它所管理的锁对象会一直保持上锁状态;而lock_guard的生命周期结束之后,它所管理的锁对象会被解锁。程序员可以非常方便地使用lock_guard,而不用担心异常安全问题。

std::lock_guard在构造时只被锁定一次,并且在销毁时解锁。
关于std::mutex的基础介绍可以参考: http://blog.csdn.net/fengbingchun/article/details/73521630


先来个小例子吧:

  1. mutex m;
  2. m.lock();
  3. sharedVariable= getVar();
  4. m.unlock();• 1

在这点代码中,互斥体m确保关键部分sharedVariable= getVar();的访问是顺序的。
顺序意味着:在这种特殊情况下,每个线程按顺序获得对关键部分的访问。
代码很简单,但容易出现死锁。如果关键部分抛出异常或程序员只是忘记解锁互斥锁,则会出现死锁。


使用std::lock_guard,我们可以做到更优雅:

  1. {
  2. std::mutex m,
  3. std::lock_guard<std::mutex> lockGuard(m);
  4. sharedVariable= getVar();
  5. }

这很容易。但是开括号 { 和闭括号 }是啥?
为了保证std::lock_guard生命周期只在这{}里面有效。
也就是说,当生命周期离开临界区时,它的生命周期就结束了。
确切地说,在那个时间点,std::lock_guard的析构函数被调用,是的,互斥体被释放了。过程是全自动的,此外,如果getVar()在sharedVariable = getVar()抛出异常时也会发生。当然,函数体范围或循环范围也限制了对象的生命周期。

什么是锁?

锁是用来保护共享资源(变量或者代码)不被并发访问的一种方法,它只是方法,实际的实现就是 std::mutex 等等的类了。
可以简单的理解为:

  1. 当前线程访问一个变量之前,将这个变量放到盒子里锁住,并且当前线程拿着钥匙。这样一来,如果有其他的线程也要访问这个变量,则必须等待当前线程将盒子解锁之后才能访问,之后其他线程在访问这个变量之前也将会再次锁住这个变量。
  2. 当前线程执行完后,就将该盒子解锁,这样其他的线程就可以拿到盒子的钥匙,并再次加锁访问这个变量了。

这样就保证了同一时刻只有一个线程可以访问共享资源,解决了简单的线程安全问题。
什么,你还没有遇到过线程安全问题?下面开始我的表演…

参考文献