c++ 17引入了一个新的锁类std::scoped_lock。

从文档中判断,它看起来类似于现有的std::lock_guard类。

有什么区别,什么时候使用?


当前回答

迟到的回答,主要是为了回应:

你可以认为std::lock_guard已弃用。

对于需要只锁定一个互斥量的常见情况,std::lock_guard有一个比scoped_lock更安全的API。

例如:

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

上面的代码片段很可能是一个意外的运行时错误,因为它编译后什么也不做。编码员的意思可能是:

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

现在它锁定/解锁mut。

如果在上面的两个示例中使用了lock_guard,那么第一个示例是编译时错误,而不是运行时错误,第二个示例与使用scoped_lock的版本具有相同的功能。

所以我的建议是使用最简单的工具:

Lock_guard如果你需要在整个作用域中只锁定一个互斥锁。 Scoped_lock(如果需要锁定多个互斥对象,而不是恰好为1)。 如果需要在块范围内解锁(包括使用condition_variable),则使用Unique_lock。

这个建议并不意味着scoped_lock应该被重新设计为不接受0互斥对象。存在一些有效的用例,其中scoped_lock需要接受可变的模板参数包,这些模板参数包可能为空。空箱子不应该锁定任何东西。

这就是不弃用lock_guard的原因。Scoped_lock和unique_lock可能是lock_guard功能的超集,但这是一把双刃剑。有时,一个类型不能做什么(在本例中是默认构造)也同样重要。

其他回答

scoped_lock是严格意义上的lock_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);
}

vs.

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);
}

std::scoped_lock的存在意味着,在c++17之前使用std::lock的大多数情况下,现在可以使用std::scoped_lock来编写,错误的可能性更小,这只能是一件好事!

迟到的回答,主要是为了回应:

你可以认为std::lock_guard已弃用。

对于需要只锁定一个互斥量的常见情况,std::lock_guard有一个比scoped_lock更安全的API。

例如:

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

上面的代码片段很可能是一个意外的运行时错误,因为它编译后什么也不做。编码员的意思可能是:

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

现在它锁定/解锁mut。

如果在上面的两个示例中使用了lock_guard,那么第一个示例是编译时错误,而不是运行时错误,第二个示例与使用scoped_lock的版本具有相同的功能。

所以我的建议是使用最简单的工具:

Lock_guard如果你需要在整个作用域中只锁定一个互斥锁。 Scoped_lock(如果需要锁定多个互斥对象,而不是恰好为1)。 如果需要在块范围内解锁(包括使用condition_variable),则使用Unique_lock。

这个建议并不意味着scoped_lock应该被重新设计为不接受0互斥对象。存在一些有效的用例,其中scoped_lock需要接受可变的模板参数包,这些模板参数包可能为空。空箱子不应该锁定任何东西。

这就是不弃用lock_guard的原因。Scoped_lock和unique_lock可能是lock_guard功能的超集,但这是一把双刃剑。有时,一个类型不能做什么(在本例中是默认构造)也同样重要。

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

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

在此之前,您必须使用std::lock以一种安全的方式锁定多个互斥对象。

范围锁的添加使它更容易使用,并避免了相关的错误。你可以认为std::lock_guard已弃用。std::scoped_lock的单参数情况可以实现为专门化,因此您不必担心可能的性能问题。

GCC 7已经支持std::scoped_lock,可以在这里看到。

要了解更多信息,你可能想要阅读标准论文