在新作用域中使用unique_lock是否等效于在使用共享资源的工作结束时解锁调用



当开发人员在新的作用域中使用std::unique_lock自动解锁互斥对象时,我看到了很多代码示例:

...
// do some staff
{
std::unique_lock<std::mutex> lock(shared_resource_mutex);
// do some actions with shared resource
}
// do some staff
...

在我看来,最好使用std::unique_lockAPI中的方法unlock以这种方式实现这种行为:

...
// do some actions
std::unique_lock<std::mutex> lock(shared_resource_mutex);
// do some actions with shared resource
lock.unlock();
// do some actions
...

这两段代码等价吗?开发人员使用第一个变体的目的是什么?也许是为了强调(使用括号(不能并行执行的代码?

我认为前一种方法更安全、更一致、更易于阅读。

首先考虑安全:

void function()
{
std::unique_lock<std::shared_mutex> lock(mtx);
// exclusive lock stuff
lock.unlock();
//  std::shared_lock<std::shared_mutex> lock(mtx); // whoops name in use
std::shared_lock<std::shared_mutex> lock2(mtx);
// read only shared lock stuff here
lock2.unlock(); // what if I forget to do this?
lock.lock(); // if I forgot to call lock2.unlock() undefined behavior
// back to the exclusive stuff
lock.unlock();  
}

如果您有不同的锁要获取/释放,并且忘记调用unlock(),那么您最终可能会尝试从同一线程锁定同一互斥两次

这是未定义的行为,所以它可能会被忽视,但会引起麻烦。

如果在错误的锁变量上调用lock()unlock(),该怎么办。。。。(比如说lock2而不是lock1?(这种可能性令人恐惧。

一致性:

此外,并不是所有的锁类型都有.unlock()函数(std::scoped_lock,std::lock_guard(,因此最好与您的编码风格保持一致。

更易于阅读:

还可以更容易地看到哪些代码部分使用锁,这使得对代码的推理更简单:

void function()
{
{
std::unique_lock<std::shared_mutex> lock(mtx);
// exclusive lock stuff
}
{
std::shared_lock<std::shared_mutex> lock(mtx);
// read only shared lock stuff here
}
{
std::unique_lock<std::shared_mutex> lock(mtx);
// back to the exclusive stuff
}
}

当对象在作用域的末尾被销毁时,锁被释放。这就是RAII的全部意义。

使用RAII的好处是,你不能忘记解锁,如何离开范围也无关紧要。例如,如果抛出异常,锁仍将被释放。

如果您只需要在构造时锁定,在销毁时解锁,那么std::scoped_lock是一个更简单/更合适的类。

您的两种方法都是正确的,您可以根据情况选择其中一种。例如,当使用condition_variable/lock组合时,能够显式锁定和解锁锁通常很有用。

这是另一种我发现既有表现力又安全的方法:

#include <mutex>
template<class Mutex, class Function>
decltype(auto) with_lock(Mutex& m, Function&& f)
{
std::lock_guard<Mutex> lock(m);
return f();
}
std::mutex shared_resource_mutex;
void something()
{
with_lock(shared_resource_mutex, [&]
{
// some actions
});
// some other actions
}

最新更新