为什么要把 std::lock 放在 std::lock_guard 之前

Why put std::lock before std::lock_guard

继续 Concurrency In Action 我已经达到了以下示例。
作者说如果我们每次锁定 2 mutexes in the same order, then we are guaranteed to avoid deadlocks.
考虑书中的这个例子:

class X
{
    private:
    some_big_object some_detail;
    std::mutex m;
public:
    X(some_big_object const& sd):some_detail(sd){}
    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);
    }
};
  1. 为什么我们要应用 std::lock and then apply 2 std::lock_guards with std::adopt_lock 而不是一个接一个地应用 2 std::lock_guards
  2. 为什么我们不能只放这 2 std::mutexes in the std::scoped_lock

std::lock 不是 RAII。不在 RAII 中的互斥锁是危险和可怕的。如果抛出异常,您可以 "leak" 锁定。

std::lock_guard 不支持死锁安全多重互斥锁。但它是 RAII,因此它使其余代码更安全。如果先在一个位置锁定 a,然后锁定 b,然后在另一个位置锁定 b,然后锁定 a,就会得到可能死锁的代码(一个线程持有 a 并等待 b,而另一个线程持有 b 并等待 a)。

std::lock 保证通过某种未指定的方式(可能包括锁的全局顺序)避免这种情况。

std::scoped_lock. In 您应该使用它来代替您显示的示例代码。添加它是因为编写该代码很糟糕。名称重整和链接问题阻止了简单地向现有锁定原语(如锁守卫)添加可变参数支持,这就是它具有不同名称的原因。

  1. 原因是 std::lock 以某种未指定的顺序锁定互斥量,但所有线程中的顺序都是相同的,从而保护我们免受死锁。因此,它可能是 lock(lhs.m),然后是 lock(rhs.m),或者反过来。这意味着我们不知道先创建 std::lock_guard 中的哪一个:lhs.m 还是 rhs.m

  2. 这本书好像是以C++11为基础标准写的。 std::scoped_lock 仅出现在 C++17 中。

Why do we apply the std::lock and then apply 2 std::lock_guards with std::adopt_lock instead of just applying 2 std::lock_guards one after another??

如果您使用两个 std::lock_guard without std::lockswap(a, b); 的锁定顺序将与 swap(b, a); 相反,其中 abX秒。如果一个线程尝试 swap(a, b); 而另一个线程尝试 swap(b, a); 它们可能会死锁。第一个线程将拥有 a 的互斥锁并等待 b,而第二个线程将拥有 b 的互斥锁并等待 a的。使用 std::lock 确保锁定顺序始终一致。

Why cant we just put this 2 std::mutexes in the std::scoped_lock??

如果您查看所链接文章的发布日期,c++17 还不存在。由于std::scoped_lock是c++17引入的,所以不能在文章中使用。这种锁定问题是 std::scoped_lock 旨在解决的问题,应该在现代代码中使用。