我正在学习 C++,我看到作用域锁的源代码非常简单。. 它是如何工作的,这是“资源获取即实例化”(RAII)的一个例子吗?
3 回答
这是说明作用域锁的小代码:
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 (Resource Acquisition Is Initialisation) 的理念是创建一个对象并对其进行初始化,这些操作结合在一起成为一个不可分割的动作。这通常意味着它们在对象的构造函数中执行。
作用域锁的工作原理是在构造互斥锁时锁定互斥锁,并在销毁互斥锁时解锁。C++ 规则保证当控制流离开作用域时(甚至通过异常),正在退出的作用域的本地对象被正确地破坏。这意味着使用范围锁而不是手动调用lock()
,并且不可能意外地不解锁互斥锁,例如,当在和unlock()
之间的代码中间抛出异常时。lock()
unlock()
这个原则适用于所有获取需要释放的资源的场景,而不仅仅是锁定互斥锁。为具有类似语法的其他操作提供此类“范围保护”类是一种很好的做法。
例如,我最近研究了一个数据结构类,它通常在修改时发送信号,但是对于一些批量操作必须禁用这些。提供一个作用域保护类,在构造时禁用它们并在销毁时重新启用它们,防止对禁用/启用函数的潜在不平衡调用。
基本上它是这样工作的:
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) 容易出错的。此外,您不能在没有范围保护的情况下在返回后释放互斥锁。