C++并发实战9:unique_lock

        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的状态。

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

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

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

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

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

6 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)

7 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尝试加锁

8 unique_lock (const unique_lock&) = delete;//禁止拷贝构造

9 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", 100 );
  bank_account Account2( "User2", 50 );
  thread t1( [&](){ transfer( Account1, Account2, 10 ); } );//lambda表达式
  thread t2( [&](){ transfer( Account2, Account1, 5 ); } );
  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 1;
            cout<<"locking..."<<endl;
        }
    }catch(int){
        cout<<"catch..."<<endl;
    }
    if(!one.try_lock()&&!two.try_lock())
        cout<<"failure"<<endl;
    else
        cout<<"success"<<endl;
    return 0;
}

程序输出:

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 0;
}



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




  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值