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.

 

posted @ 2022-05-20 11:31  strive-sun  阅读(128)  评论(0编辑  收藏  举报