我正在学习c++,我看到作用域锁的源代码非常简单。它是如何工作的,这是"资源获取即实例化"(RAII)的一个例子吗?
下面是一小段代码来说明作用域锁:
void do_something()
{
//here in the constructor of scoped_lock, the mutex is locked,
//and a reference to it is kept in the object `lock` for future use
scoped_lock lock(shared_mutex_obj);
//here goes the critical section code
}//<---here : the object `lock` goes out of scope
//that means, the destructor of scoped_lock will run.
//in the destructor, the mutex is unlocked.
阅读评论。这就解释了scoped_lock是如何工作的。
scoped_lock
通常是这样实现的(最少的代码):
class scoped_lock : noncopyable
{
mutex_impl &_mtx; //keep ref to the mutex passed to the constructor
public:
scoped_lock(mutex_impl & mtx ) : _mtx(mtx)
{
_mtx.lock(); //lock the mutex in the constructor
}
~scoped_lock()
{
_mtx.unlock(); //unlock the mutex in the constructor
}
};
RAII(资源获取即初始化)的思想是将创建对象和初始化对象连接在一起,形成一个不可分离的动作。这通常意味着它们在对象的构造函数中执行。
作用域锁的工作原理是:在互斥锁被构造时锁定它,在互斥锁被销毁时解锁它。c++规则保证当控制流离开一个作用域时(即使是通过异常),被退出的作用域的局部对象将被正确地销毁。这意味着使用作用域锁而不是手动调用lock()
和unlock()
,不可能意外地不解锁互斥锁,例如,当在lock()
和unlock()
之间的代码中间抛出异常时。
这个原则适用于所有获取必须释放的资源的场景,而不仅仅是锁定互斥锁。为其他具有类似语法的操作提供这样的"作用域保护"类是一个很好的实践。
例如,我最近研究了一个数据结构类,当它被修改时通常会发送信号,但是对于一些批量操作必须禁用这些信号。提供一个作用域保护类,在构造时禁用它们,在销毁时重新启用它们,可以防止对disable/enable函数的潜在不平衡调用。
基本上是这样的:
template <class Lockable>
class lock{
public:
lock(Lockable & m) : mtx(m){
mtx.lock();
}
~lock(){
mtx.unlock();
}
private:
Lockable & mtx;
};
如果你用
int some_function_which_uses_mtx(){
lock<std::mutex> lock(mtx);
/* Work with a resource locked by mutex */
if( some_condition())
return 1;
if( some_other_condition())
return 1;
function_witch_might_throw();
return;
}
创建一个具有基于作用域生存期的新对象。无论何时,只要当前作用域仍然存在并且这个锁被销毁,它就会自动调用mtx.unlock()
。注意,在这个特殊的例子中,互斥锁是由lock
的构造函数获得的,也就是RAIII。
如果没有瞄准镜保护装置,你会怎么做?无论何时离开该函数,都需要调用mtx.unlock()
。这是a)繁琐和b)容易出错的。如果没有作用域保护,也不能在返回后释放互斥锁。