std::lock_guard 还是 std::scoped_lock?

std::lock_guard or std::scoped_lock?

C++17 引入了一个名为 std::scoped_lock 的新锁 class。

从文档来看,它看起来类似于已经存在的 std::lock_guard class。

有什么区别,我应该在什么时候使用它?

唯一且重要的区别是 std::scoped_lock 有一个可变构造函数接受多个互斥锁。这允许以避免死锁的方式锁定多个互斥量,就好像使用了 std::lock 一样。

{
    // safely locked as if using std::lock
    std::scoped_lock<std::mutex, std::mutex> lock(mutex1, mutex2);     
}

之前,您必须像this answer.

中解释的那样,使用 std::lock 以安全的方式锁定多个互斥量。

作用域锁的加入使得这个更容易使用并且避免了相关的错误。您可以考虑弃用 std::lock_guardstd::scoped_lock 的单参数情况可以作为特化来实现,这样您就不必担心可能出现的性能问题。

GCC 7 已经支持 std::scoped_lock 可以看到 here.

有关更多信息,您可能需要阅读 standard paper

scoped_locklock_guard 的严格高级版本,它一次锁定任意数量的互斥量(使用与 std::lock 相同的死锁避免算法)。在新代码中,你应该只使用 scoped_lock.

lock_guard 仍然存在的唯一原因是兼容性。它不能被删除,因为它在当前代码中使用。此外,事实证明改变它的定义(从一元到可变)是不可取的,因为这也是一个可观察的,因此是破坏性的变化(但出于某种技术原因)。

这是来自 C++ Concurrency in Action:

的示例和引用
friend void swap(X& lhs, X& rhs)
{
    if (&lhs == & rhs)
        return;
    std::lock(lhs.m, rhs.m);
    std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock);
    std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock);
    swap(lhs.some_detail, rhs.some_detail);
}

对比

friend void swap(X& lhs, X& rhs)
{
    if (&lhs == &rhs)
        return;
    std::scoped_lock guard(lhs.m, rhs.m);
    swap(lhs.some_detail, rhs.some_detail);
}

The existence of std::scoped_lock means that most of the cases where you would have used std::lock prior to c++17 can now be written using std::scoped_lock, with less potential for mistakes, which can only be a good thing!

回答晚了,主要是为了回应:

You can consider std::lock_guard deprecated.

对于需要恰好锁定一个互斥体的常见情况,std::lock_guard 有一个 API 比 scoped_lock.

使用起来更安全一点

例如:

{
   std::scoped_lock lock;  // protect this block
   ...
}

上面的代码片段很可能是 运行 时的意外错误,因为它编译后完全没有执行任何操作。编码器可能意味着:

{
   std::scoped_lock lock{mut};  // protect this block
   ...
}

现在它locks/unlocks mut.

如果在上面的两个示例中使用 lock_guard,则第一个示例是编译时错误而不是 运行 时错误,第二个示例与使用 scoped_lock.

的版本

所以我的建议是使用最简单的工具来完成这项工作:

  1. lock_guard 如果您需要为整个作用域精确锁定 1 个互斥量。

  2. scoped_lock 如果您需要锁定多个不正好为 1 的互斥量。

  3. unique_lock 如果您需要在方块范围内解锁(包括使用 condition_variable)。

这个建议 暗示 scoped_lock 应该重新设计为不接受 0 个互斥体。存在有效的用例,其中希望 scoped_lock 接受可能为空的可变参数模板参数包。空箱应该锁定任何东西。

这就是 lock_guard 未被弃用的原因。 scoped_lock unique_lock 可能是 lock_guard 功能的超集,但这是一把双刃剑。有时,类型 不会 做什么同样重要(在这种情况下为默认构造)。