【问题标题】:Potential deadlock in thread-safe stack C++线程安全堆栈 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() 由已经拥有互斥锁的线程调用,那不是未定义的行为吗?

【问题讨论】:

    标签: c++ multithreading concurrency


    【解决方案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 而不是死锁。

    了解recursiveshared 互斥锁。

    【讨论】:

      【解决方案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。代码没有问题。

        【讨论】:

          猜你喜欢
          • 2010-10-21
          • 2011-05-08
          • 1970-01-01
          • 2020-08-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-04-08
          相关资源
          最近更新 更多