全自动固然好,但在一些特定环境下,如果没办法全自动,那也不代表就马上变成“全手动”操作,这中间还有一个很科学的“半自动”过程。
lock_guard <T>是封装互斥量得到一把全自动化的锁,变量定义时自动上锁,变量析构时自动解锁。
到“原子操作”时,就会有该全自动锁无法适用的代码结构。
unique_lock <T> 是标准库提供的“半自动化锁”模板,具备lock_guard <T>的全部功能:
......
__concurrency_mutex_block_begin_
{
//“_m”是mutex对象
std::unique_lock <std::mutex> lock(_m);
......
}
......
lock定义时,自动为互斥量“_m”加锁,析构时自动解锁。接下来就是各种半自动功能所要解决的问题了。
问1:
假设手上的互斥量已经加上锁了,但希望通过一个lock对象帮助自动解锁,怎么办?比如有这么一段代码:
std::mutex M;
M.lock(); //种原因,这里M就是自行锁上了
......
if(...)
{
M.unlock();
return;
}
try
{
......
M.unlock();
return;
}
catch(...)
{
M.unlock();
return;
}
函数有几个出口,就写了几处的解锁操作,这太讨厌了,此局何解?
答1:
如无必要,显然还是要避免M擅自加锁。不过如果无可避免,那么只能面对现实。此时我们定义一个特殊的unique_lock <T>对象:
std::mutex M;
M.lock();//种种原因,这里M就是自行锁上了
......
std::unique_lock <std::mutex> lock(_m, std::adopt_lock);
......//后面不需要任何手工解锁代码
adopt意为“收养、领养”。用它作第二个入参,构造一把锁,将认定接受的互斥量已经上错,不会再次上锁!
这个功能,std::lock_guard <T>也具备。
问2:
接受一个未上锁的互斥量,但构造时不对它上锁,std::unique_lock能做到吗?又问,std::lock_guard能做到吗?
答2:
std::unique_lock可以做到。第二个入参改为std::defer_lock,则构造时不对互斥量调用lock()操作。这个本事,std::lock_guard不具备。
......
//暂不加锁
std::unique_lock <std::mutex> lock(_m, std::defer_lock);
......
//过一会儿才加锁
lock.lock();
......//后续自动解锁功能依旧
问3:
可不可以完全通过unique_lock <T>实现手工对互斥量加锁、解锁?
答3:
完全可以。前例已有unique_lock<T>对象调用lock()的方法。对用也可以手工调用unlock()方法。unique_lock <T>对象在析构时会自动判断是否还需解锁。