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. 004.RAID删除

    一 卸载RAID [root@kauai ~]# umount /dev/md0 #卸载挂载点 二 停止RAID设备 [root@kauai ~]# mdadm -S /dev/md0 #停用RAID ...

  2. Codeforces Round 542 (Div. 2)

    layout: post title: Codeforces Round 542 (Div. 2) author: "luowentaoaa" catalog: true tags ...

  3. 美团开源Graver框架:用“雕刻”诠释iOS端UI界面的高效渲染

    Graver 是一款高效的 UI 渲染框架,它以更低的资源消耗来构建十分流畅的 UI 界面.Graver 独创性的采用了基于绘制的视觉元素分解方式来构建界面,得益于此,该框架能让 UI 渲染过程变得更 ...

  4. BZOJ.1024.[SCOI2009]生日快乐(记忆化搜索)

    题目链接 搜索,枚举切的n-1刀. 对于长n宽m要切x刀,可以划分为若干个 长n'宽m'要切x'刀 的子问题,对所有子问题的答案取max 对所有子问题的方案取min 就是当前状态答案. 这显然是会有很 ...

  5. MVVM模式下关闭窗口的实现

    通过行为来实现 实现界面与逻辑的分离 窗口关闭行为:其中含有布尔型的Close属性,将相应的关闭行为绑定到该属性上,则可以实现窗口的关闭行为,从而实现VM与View的分离 public class W ...

  6. NOI.AC NOIP模拟赛 第二场 补记

    NOI.AC NOIP模拟赛 第二场 补记 palindrome 题目大意: 同[CEOI2017]Palindromic Partitions string 同[TC11326]Impossible ...

  7. LPC LINK2 IO CONNECTOR

  8. USBDM Kinetis Debugger and Programmer

    Introduction The FRM-xxxx boards from Freescale includes a minimal SWD based debugging interface for ...

  9. delphi 取上级目录

    var  S: string;begin  S := 'c:\aa\bb\cc\dd\abc.exe';  ShowMessage(ExtractFileDir(ExtractFileDir(S))) ...

  10. Delph 两个对立程序使用消息进行控制通信

    在实际应用中,总是会遇到两个独立的程序进行通信,其实通信的方式有好几种,比如进程间通信,消息通信. 项目中用到了此功能, 此功能用于锁屏程序, 下面把实现的流程和大家分享一下. 1. 在锁屏程序中,自 ...