簡體   English   中英

線程安全堆棧 C++ 中的潛在死鎖

[英]Potential deadlock in thread-safe stack C++

在“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() 由已經擁有互斥鎖的線程調用,那不是未定義的行為嗎?

當進入 pop() 時獲得鎖的線程正在調用也受互斥鎖保護的 empty() 時,這段代碼如何不會陷入死鎖?

因為您沒有調用threadsafe_stackempty成員 function ,但是您正在調用 class std::stack<T>的 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();
}

然后,這將是未定義的行為

如果鎖由已經擁有互斥鎖的線程調用,則行為未定義:例如,程序可能死鎖。 鼓勵可以檢測到無效使用的實現拋出帶有錯誤條件 resource_deadlock_would_occur 的 std::system_error 而不是死鎖。

了解遞歸共享互斥鎖。

如果pop會調用this->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的成員 function empty的方法。 代碼沒有問題。

不是 100% 確定你的意思,我猜你的意思是在同一個線程中按順序調用popempty

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

std::lock_guard遵循 RAII。 這意味着構造函數

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

將獲取/鎖定互斥鎖,而析構函數(當lock超出范圍時)將再次釋放/解鎖互斥鎖。 所以它在下一次 function 調用時解鎖。

pop中只調用了data.empty() ,它不受互斥體的保護。 pop中調用this->empty()確實會導致未定義的行為。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM