Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Potential deadlock in thread-safe stack C++

In the book "Concurrency in Action", there's an implementation of thread-safe stack where mutex is acquired/locked upon entering pop() and empty() functions like shown below:

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();
      }
};

My question is, how does this code not run into a deadlock, when a thread, that has acquired the lock upon entering pop() is calling empty() which is protected by mutex as well? If lock() is called by the thread that already owns the mutex, isn't that undefined behavior?

like image 604
meriam Avatar asked Oct 23 '25 15:10

meriam


2 Answers

how does this code not run into a deadlock, when a thread, that has acquired the lock upon entering pop() is calling empty() which is protected by mutex as well?

Because you are not calling empty member function of threadsafe_stack but you are calling empty() of class std::stack<T>. If the code would be:

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();
}

Then, it would be undefined behavior:

If lock is called by a thread that already owns the mutex, the behavior is undefined: for example, the program may deadlock. An implementation that can detect the invalid usage is encouraged to throw a std::system_error with error condition resource_deadlock_would_occur instead of deadlocking.

Learn about recursive and shared mutex.

like image 192
abhiarora Avatar answered Oct 25 '25 05:10

abhiarora


Not 100% sure what you mean, I guess you mean calling pop and empty sequentially in the same thread? Like in

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

std::lock_guard follows RAII. This means the constructor

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

will acquire/lock the mutex and the destructor (when lock goes out of scope) will release/unlock the mutex again. So it's unlocked at the next function call.

Inside pop only data.empty() is called, which is not protected by a mutex. Calling this->empty() inside pop would indeed result in undefined behaviour.

like image 36
churill Avatar answered Oct 25 '25 06:10

churill