C++ 多线程的错误和如何避免(7)
要以相同顺序获取多个锁
多线程在加锁解锁时,可能会出现死锁问题,比如,
线程 1 在加锁 mutex A 后,继续尝试获取 mutex B,而 mutex B 已经被线程 2 获取,而线程 2 在等待获取 mutex A,mutex B 只有线程 2 获取 mutex A 后才能解锁,
这就导致线程 1 和线程 2 互相等待锁,而这一操作就是死锁情况,容易导致程序挂起。
使用代码模拟死锁场景:
#include <iostream> #include <string> #include <thread> #include <mutex> using namespace std; std::mutex muA; std::mutex muB; void CallHome_AB(string message) { muA.lock(); //Some additional processing std::this_thread::sleep_for(std::chrono::milliseconds(100)); muB.lock(); cout << "Thread " << this_thread::get_id() << " says " << message << endl; muB.unlock(); muA.unlock(); } void CallHome_BA(string message) { muB.lock(); //Some additional processing std::this_thread::sleep_for(std::chrono::milliseconds(100)); muA.lock(); cout << "Thread " << this_thread::get_id() << " says " << message << endl; muA.unlock(); muB.unlock(); } int main() { thread t1(CallHome_AB, "Hello from Jupiter"); thread t2(CallHome_BA, "Hello from Pluto"); t1.join(); t2.join(); return 0; }
运行后,会发现控制台没有打印任何字符串,因为两个线程都尝试获取对方的锁,导致了死锁
如何解决?
最好的办法就是所有的锁都可以以相同的顺序获得,比如线程 1 顺序获得 mutex A 和 mutex B,再释放 mutex B 和 mutex A,线程 2 也顺序获得 mutex A 和 B,再顺序释放
std::mutex muA; std::mutex muB; void CallHome_AB(string message) { muA.lock(); // Some additional processing std::this_thread::sleep_for(std::chrono::milliseconds(100)); muB.lock(); cout << "Thread " << this_thread::get_id() << " says " << message << endl; muB.unlock(); muA.unlock(); } void CallHome_BA(string message) { muA.lock(); // Some additional processing std::this_thread::sleep_for(std::chrono::milliseconds(100)); muB.lock(); cout << "Thread " << this_thread::get_id() << " says " << message << endl; muB.unlock(); muA.unlock(); }
当然目前也有封装好的 C++ 函数来同时获得两个锁,如下
std::scoped_lock lock{muA, muB};
The class scoped_lock
is a mutex wrapper that provides a convenient RAII-style mechanism for owning one or more mutexes for the duration of a scoped block.
When a scoped_lock
object is created, it attempts to take ownership of the mutexes it is given. When control leaves the scope in which the scoped_lock
object was created, the scoped_lock
is destructed and the mutexes are released. If several mutexes are given, deadlock avoidance algorithm is used as if by std::lock.
或者我们也可以用 std::timed_mutex 来解决这个问题,当超过一定时间后自动释放锁
The timed_mutex
class is a synchronization primitive that can be used to protect shared data from being simultaneously accessed by multiple threads.