25 June 2023

C++ 核心指南目录

“Use std::lock() or std::scoped_lock to acquire multiple mutexes”

理由

例子

以下例子会导致死锁:

// thread 1
lock_guard<mutex> lck1(m1);
lock_guard<mutex> lck2(m2);

// thread 2
lock_guard<mutex> lck2(m2);
lock_guard<mutex> lck1(m1);

应该使用 lock():

// thread 1
lock(m1, m2);
lock_guard<mutex> lck1(m1, adopt_lock);
lock_guard<mutex> lck2(m2, adopt_lock);

// thread 2
lock(m2, m1);
lock_guard<mutex> lck2(m2, adopt_lock);
lock_guard<mutex> lck1(m1, adopt_lock);

或者用 C++17 的机制:

// thread 1
scoped_lock<mutex, mutex> lck1(m1, m2);

// thread 2
scoped_lock<mutex, mutex> lck2(m2, m1);

这里,开发 thread1thread2 的人仍然没有协调互斥锁的顺序,但是顺序已经不重要。

注意

在实际代码中,互斥锁的名字不一定能告诉程序员它的使用目的,以及获取顺序。在实际代码中,互斥锁并不总是在连续的几行代码中获取。

在 C++17 中,可以简单的这样写:

lock_guard lck1(m1, adopt_lock);

然后,就能推导出互斥锁的类型。

强化

检测出捕获多个互斥锁的地方。尽管不太好判断,但是捕捉前面介绍的简单例子相对还方便。