线程安全堆栈 C++ 中的潜在死锁

Posted

技术标签:

【中文标题】线程安全堆栈 C++ 中的潜在死锁【英文标题】:Potential deadlock in thread-safe stack C++ 【发布时间】:2020-05-04 07:59:22 【问题描述】:

在“Concurrency in Action”一书中,有一个线程安全堆栈的实现,在进入 pop() 和 empty() 函数时获取/锁定互斥锁,如下所示:

class threadsafe_stack 
   private:
      std::stack<T> data;
      mutable std::mutex m;
   public:
      //...
      void pop(T& value) 
         std::lock_guard<std::mutex> lock(m);
         if(data.empty()) throw empty_stack();
         value = std::move(data.top());
         data.pop();
      

      bool empty() const 
         std::lock_guard<std::mutex> lock(m);
         return data.empty();
      
;

我的问题是,当一个在进入 pop() 时获得锁的线程正在调用也受互斥锁保护的 empty() 时,这段代码如何不会陷入死锁?如果 lock() 由已经拥有互斥锁的线程调用,那不是未定义的行为吗?

【问题讨论】:

【参考方案1】:

当一个在进入 pop() 时获得锁的线程正在调用也受互斥锁保护的 empty() 时,这段代码如何不会陷入死锁?

因为你没有调用threadsafe_stack 的成员函数empty,而是调用了std::stack&lt;T&gt; 类的empty()。如果代码是:

void pop(T& value) 

    std::lock_guard<std::mutex> lock(m);
    if(empty()) // instead of data.empty()
        throw empty_stack();
    value = std::move(data.top());
    data.pop();

那么,就是undefined behavior:

如果锁由已经拥有互斥锁的线程调用,则行为未定义:例如,程序可能死锁。鼓励可以检测到无效使用的实现抛出带有错误条件 resource_deadlock_would_occur 的 std::system_error 而不是死锁。

了解recursive 和shared 互斥锁。

【讨论】:

【参考方案2】:

不是 100% 确定你的意思,我猜你的意思是在同一个线程中依次调用 popempty?就像在

while(!x.empty()) x.pop();

std::lock_guard 跟随 RAII。这意味着构造函数

std::lock_guard<std::mutex> lock(m);

将获取/锁定互斥体,而析构函数(当lock 超出范围时)将再次释放/解锁互斥体。所以它会在下一次函数调用时解锁。

pop 内部只调用了data.empty(),它不受互斥体的保护。在pop 内调用this-&gt;empty() 确实会导致未定义的行为。

【讨论】:

【参考方案3】:

如果pop 会调用this-&gt;empty,那你是正确的。通过std::lock_guard 两次锁定同一个互斥锁是未定义的行为,除非锁定的互斥锁是递归的。

来自构造函数上的cppreference(示例代码中使用的那个):

有效地调用 m.lock()。如果 m 不是递归互斥体并且当前线程已经拥有 m,则行为未定义。

为了完整起见,还有第二个构造函数:

lock_guard( mutex_type& m, std::adopt_lock_t t );

哪个

获取互斥体 m 的所有权而不尝试锁定它。如果当前线程不拥有 m,则行为未定义。

但是,pop 调用data.empty,这是私有成员的方法,而不是threadsafe_stack 的成员函数empty。代码没有问题。

【讨论】:

以上是关于线程安全堆栈 C++ 中的潜在死锁的主要内容,如果未能解决你的问题,请参考以下文章

线程安全的概念,实现线程安全的几种方法

C++多线程1.2-线程安全的保证——互斥量mutex(锁)和原子变量atomic

C++多线程1.2-线程安全的保证——互斥量mutex(锁)和原子变量atomic

多线程系列八:线程安全

Java面试宝典线程安全问题|线程死锁的出现|线程安全的集合类

C++ STL容器如何解决线程安全的问题?