C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为。通常的做法是在修改共享数据成员的时候进行加锁--mutex。在使用锁的时候通常是在对共享数据进行修改之前进行lock操作,在写完之后再进行unlock操作,进场会出现由于疏忽导致由于lock之后在离开共享成员操作区域时忘记unlock,导致死锁。

针对以上的问题,C++11中引入了std::unique_lock与std::lock_guard两种数据结构。通过对lock和unlock进行一次薄的封装,实现自动unlock的功能。

 std::mutex mut;

 void insert_data()
{
std::lock_guard<std::mutex> lk(mut);
queue.push_back(data);
} void process_data()
{
std::unqiue_lock<std::mutex> lk(mut);
queue.pop();
}

std::unique_lock 与std::lock_guard都能实现自动加锁与解锁功能,但是std::unique_lock要比std::lock_guard更灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点。

1 回顾采用RAII手法管理mutex的std::lock_guard其功能是在对象构造时将mutex加锁,析构时对mutex解锁,这样一个栈对象保证了在异常情形下mutex可以在lock_guard对象析构被解锁,lock_guard拥有mutex的所有权。

 explicit lock_guard (mutex_type& m);//必须要传递一个mutex作为构造参数
lock_guard (mutex_type& m, adopt_lock_t tag);//tag=adopt_lock表示mutex已经在之前被上锁,这里lock_guard将拥有mutex的所有权
lock_guard (const lock_guard&) = delete;//不允许copy constructor

2 再来看一个与std::lock_guard功能相似但功能更加灵活的管理mutex的对象 std::unique_lock,unique_lock内部持有mutex的状态:locked,unlocked。unique_lock比lock_guard占用空间和速度慢一些,因为其要维护mutex的状态。

  unique_lock() noexcept;    //可以构造一个空的unique_lock对象,此时并不拥有任何mutex

  explicit unique_lock (mutex_type& m);//拥有mutex,并调用mutex.lock()对其上锁    

  unique_lock (mutex_type& m, try_to_lock_t tag);//tag=try_lock表示调用mutex.try_lock()尝试加锁

  unique_lock (mutex_type& m, defer_lock_t tag) noexcept;//tag=defer_lock表示不对mutex加锁,只管理mutex,此时mutex应该是没有加锁的

  unique_lock (mutex_type& m, adopt_lock_t tag);//tag=adopt_lock表示mutex在此之前已经被上锁,此时unique_locl管理mutex

  template <class Rep, class Period>
unique_lock (mutex_type& m, const chrono::duration<Rep,Period>& rel_time);//在一段时间rel_time内尝试对mutex加锁,mutex.try_lock_for(rel_time) template <class Clock, class Duration>
unique_lock (mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);//mutex.try_lock_until(abs_time)直到abs_time尝试加锁 unique_lock (const unique_lock&) = delete;//禁止拷贝构造 unique_lock (unique_lock&& x);//获得x管理的mutex,此后x不再和mutex相关,x此后相当于一个默认构造的unique_lock,移动构造函数,具备移动语义,movable but not copyable

说明:其中2和5拥有mutex的所有权,而1和4永远不用有mutex的所有权,3和6及7若尝试加锁成功则拥有mutex的所有权

unique_lock 在使用上比lock_guard更具有弹性,和 lock_guard 相比,unique_lock 主要的特色在于:
         unique_lock 不一定要拥有 mutex,所以可以透过 default constructor 建立出一个空的 unique_lock。
         unique_lock 虽然一样不可复制(non-copyable),但是它是可以转移的(movable)。所以,unique_lock 不但可以被函数回传,也可以放到 STL 的 container 里。
         另外,unique_lock 也有提供 lock()、unlock() 等函数,可以用来加锁解锁mutex,也算是功能比较完整的地方。
         unique_lock本身还可以用于std::lock参数,因为其具备lock、unlock、try_lock成员函数,这些函数不仅完成针对mutex的操作还要更新mutex的状态。

3  std::unique_lock其它成员函数

 ~unique_lock();//若unique_lock对象拥有管理的mutex的所有权,mutex没有被销毁或者unlock,那么将执行mutex::unlock()解锁,并不销毁mutex对象。
mutex_type* mutex() const noexcept;//返回unique_lock管理的mutex指针,但是unique_lock不会放弃对mutex的管理,若unique_lock对mutex上锁了,其有义务对mutex解锁
bool owns_lock() const noexcept;//当mutex被unique_lock上锁,且mutex没有解锁或析构,返回真,否则返回false
explicit operator bool() const noexcept;//同上

4  std::unique_lock增加了灵活性,比如可以对mutex的管理从一个scope通过move语义转到另一个scope,不像lock_guard只能在一个scope中生存。同时也增加了管理的难度,因此如无必要还是用lock_guard。

5 网上看见一个unique_lock的应用于银行转账的实例,贴在这里:

 #include <mutex>
#include <thread>
#include <chrono>
#include <iostream>
#include <string>
using namespace std;
struct bank_account//银行账户
{
explicit bank_account(string name, int money)
{
sName = name;
iMoney = money;
} string sName;
int iMoney;
mutex mMutex;//账户都有一个锁mutex
};
void transfer( bank_account &from, bank_account &to, int amount )//这里缺少一个from==to的条件判断个人觉得
{
unique_lock<mutex> lock1( from.mMutex, defer_lock );//defer_lock表示延迟加锁,此处只管理mutex
unique_lock<mutex> lock2( to.mMutex, defer_lock );
lock( lock1, lock2 );//lock一次性锁住多个mutex防止deadlock
from.iMoney -= amount;
to.iMoney += amount;
cout << "Transfer " << amount << " from "<< from.sName << " to " << to.sName << endl;
}
int main()
{
bank_account Account1( "User1", );
bank_account Account2( "User2", );
thread t1( [&](){ transfer( Account1, Account2, ); } );//lambda表达式
thread t2( [&](){ transfer( Account2, Account1, ); } );
t1.join();
t2.join();
}

说明:加锁的时候为什么不是如下这样的?在前面一篇博文中有讲到多个语句加锁可能导致deadlock,假设:同一时刻A向B转账,B也向A转账,那么先持有自己的锁再相互请求对方的锁必然deadlock。

 lock_guard<mutex> lock1( from.mMutex );
lock_guard<mutex> lock2( to.mMutex );

采用lock_guard也可以如下:

 lock( from.mMutex, to.mMutex );
lock_guard<mutex> lock1( from.mMutex, adopt_lock );//adopt_lock表示mutex已经上锁,lock1将拥有from.mMutex
lock_guard<mutex> lock2( to.mMutex, adopt_lock );

6 上面的例子lock针对mutex加锁后,并没有显示解锁,那么离开lock的作用域后解锁了吗?验证代码如下,在lock后抛出异常mutex解锁了吗?:

 #include<mutex>
#include<exception>
#include<iostream>
using namespace std;
int main(){
mutex one,two;
try{
{
lock(one,two);
throw ;
cout<<"locking..."<<endl;
}
}catch(int){
cout<<"catch..."<<endl;
}
if(!one.try_lock()&&!two.try_lock())
cout<<"failure"<<endl;
else
cout<<"success"<<endl;
return ;
}

程序输出:

catch...
success          //lock后的操作抛出异常后,mutex解锁了

7 unique_lock is movable but not copyable.因此可以作为函数返回值,STL容器元素。例如:一个函数采用unique_lock加锁mutex然后准备好数据并将unique_lock返回给调用者,调用者在mutex保护下对数据进一步加工,简单的代码如下:

 #include<mutex>
#include<iostream>
using namespace std;
mutex m;
unique_lock<mutex> get_lock(){
unique_lock<mutex> lk(m);
cout<<"prepare data..."<<endl;//准备数据
return lk;//移动构造
}
int main(){
unique_lock<mutex> lk(get_lock());
cout<<"process data..."<<endl;//在mutex保护下数据深加工
return ;
}

8 unique_lock::lock(), unique_lock::unlock()这一组成员函数充分说明了,unique_lock在构造时不必对mutex加锁且可以在后期某个时候对mutex加锁; unique_lock可以在自己实例销毁前调用unique_lock::unlock()提前释放锁,这对于一些分支语句中可能得到性能提升。

C++11 std::unique_lock与std::lock_guard区别及多线程应用实例的更多相关文章

  1. C++ 11 多线程下std::unique_lock与std::lock_guard的区别和用法

    这里主要介绍std::unique_lock与std::lock_guard的区别用法 先说简单的 一.std::lock_guard的用法 std::lock_guard其实就是简单的RAII封装, ...

  2. std::unique_lock与std::lock_guard分析

    背景 C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢,导致程序出现未定义或异常行为.通常的做法是在修改共享数据成员时进行加锁(mutex).在使用锁 ...

  3. C++ 并发编程,std::unique_lock与std::lock_guard区别示例

    背景 平时看代码时,也会使用到std::lock_guard,但是std::unique_lock用的比较少.在看并发编程,这里总结一下.方便后续使用. std::unique_lock也可以提供自动 ...

  4. std::unique_lock与std::lock_guard区别示例

    std::lock_guard std::lock_guard<std::mutex> lk(frame_mutex); std::unique_lock<std::mutex> ...

  5. std::unique_lock<std::mutex> or std::lock_guard<std::mutex> C++11 区别

    http://stackoverflow.com/questions/20516773/stdunique-lockstdmutex-or-stdlock-guardstdmutex The diff ...

  6. std::lock_guard/std::unique_lock

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为.通常的做法是在修改共享数据成员的时候进行加锁--mutex.在使用锁的时候通 ...

  7. 基于std::mutex std::lock_guard std::condition_variable 和std::async实现的简单同步队列

    C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为.通常的做法是在修改共享数据成员的时候进行加锁--mutex.在使用锁的时候通 ...

  8. boost::unique_lock和boost::lock_guard的区别

    lock_guard unique_lock boost::mutex mutex; boost::unique_lock<boost::mutex> lock(mutex); std:: ...

  9. C++11 并发指南五(std::condition_variable 详解)

    前面三讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解)>分别介绍了 std::thread,std::mut ...

随机推荐

  1. Android-IntentFilter

    Android-IntentFilter 学习自 <Android开发艺术探索> IntentFilter漫谈 众所周知,在Android中如果要想启动一个Activity,有两种方式,显 ...

  2. 牛客OI赛制测试赛3游记

    A - 数字权重 题目大意: 一个\(n\)位的数字.设第\(i\)位的数为\(a_i\),其中\(a_1\)为最高位,\(a_n\)为最低位,\(k\)为给定的数字.求同时满足满足以下两个条件的数的 ...

  3. Fiddler_解决Fiddler查看Post参数中文乱码的问题

    解决Fiddler查看Post参数中文乱码的问题 今天一个同事问我,为什么用Fiddler查看Post的中文参数,是一堆乱码,如下: 需要在注册表中增加一个键值: HKEY_CURRENT_USER\ ...

  4. Uniscribe相关文章

    相关资料很少 http://msdn.microsoft.com/en-us/library/windows/desktop/dd374127(v=vs.85).aspx http://www.cnb ...

  5. asp.net core读取appsettings.json,如何读取多环境开发配置

    摘要 在读取appsettings.json文件中配置的时候,觉得最简单的方式就是使用asp.net core注入的方式进行读取了. 步骤 首先根据配置项的结构定义一个配置类,比如叫AppSettin ...

  6. Java嵌入式数据库H2学习总结(二)——在Web应用程序中使用H2数据库

    一.搭建测试环境和项目 1.1.搭建JavaWeb测试项目 创建一个[H2DBTest]JavaWeb项目,找到H2数据库的jar文件,如下图所示: H2数据库就一个jar文件,这个Jar文件里面包含 ...

  7. SATA工作模式咋选?揭秘AHCI和IDE区别(全文)

    第1页:AHCI模式与Win7.SSD的不解之缘     AHCI这个注定和SATA接口结下不解之缘的接口模式,它担负着淘汰IDE模式的重任,从诞生开始就充满争议,它经历了整整7年时间.它伴随着SSD ...

  8. Unity3D实践系列09, 物理引擎与碰撞检测

    在Unity3D中,一个物体通常包含一个Collider和一个Rigidbody.Collider是碰撞体,一个物体是Collider,才可以进行碰撞检测.Collider组件中的"Is T ...

  9. 初识序列化和反序列化,使用BinaryFormatter类、ISerializable接口、XmlSerializer类进行序列化和反序列化

    序列化是将对象转换成字节流的过程,反序列化是把字节流转换成对象的过程.对象一旦被序列化,就可以把对象状态保存到硬盘的某个位置,甚至还可以通过网络发送给另外一台机器上运行的进程.本篇主要包括: ● 使用 ...

  10. 委托、Lambda表达式、事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性

    委托是一个类. namespace ConsoleApplication1 { internal delegate void MyDelegate(int val); class Program { ...