Non-Profit, International

Spirit unsterblich.

C++ std::lock 和死锁

字数统计:618 blog

死锁是多线程同时访问多个共享对象的常见问题,其本质是当两个线程各自持有一份数据,又再次想要各自持有另外的数据,但是这组新的数据又被对方持有,造成相互的资源竞争。

C++ 提供了 std::lock 以提供一种解决死锁的方案:即对于两个资源,使其同时锁定或者等候的操作具有原子性,这样就消除了由于顺序而产生的死锁。

参考:C++ 并发编程实战 第二版


#include <mutex>

class some_big_object;

void swap(some_big_object &lhs, some_big_object &rhs);

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

void swap(X &lhs, X &rhs)
{
    if (&lhs == &rhs)
        return;
    std::lock(lhs.m, rhs.m);                                    // 1
    std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock); // 2
    std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock); // 3
    swap(lhs.some_detail, rhs.some_detail);
}

由于 C++ 的许多语句看上去是一条,但是实际上是多条,并且大多数逗号运算符左右执行顺序是不确定的,以及 CPU 执行不同指令需要用到的时间不一样,导致许多操作都不具有原子性。

而 std::lock 就具有原子性。

由于 C++11 的 std::lock 只有锁定功能,没有解锁功能,所以需要将 std::mutex 的所有权移交给 std::lock_guard。

C++17 中添加了 RAII 风格的 std::lock:std::scoped_lock,简化了代码的书写:


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

std::scoped_lock 是 std::lock 和 std::lock_guard 的结合体,可以同时传入两个 std::mutex 并在离开作用域时自动析构,防止死锁。


若无特殊声明,本文以 CC BY-SA 3.0 许可协议 提供。