1 In order for a thread to safely delete a node, you need to ensure that you’re preventing concurrent accesses to three nodes: the node being deleted and the nodes on either side.
2 Not only is it safe in single-threaded code, it’s expected: calling top() on an empty stack is undefined behavior. With a shared stack object, this call sequence is no longer
safe, because there might be a call to pop() from another thread that removes the last element in between the call to empty() B and the call to top() c . This is therefore a Sharing data between threads classic race condition, and the use of a mutex internally to protect the stack contents doesn’t prevent it; it’s a consequence of the interface.
stack<int> s;
if(!s.empty())
{
int const value=s.top();
s.pop();
do_something(value);
}
2 Not only is it safe in single-threaded code, it’s expected: calling top() on an empty stack is undefined behavior. With a shared stack object, this call sequence is no longer
safe, because there might be a call to pop() from another thread that removes the last element in between the call to empty() B and the call to top() c . This is therefore a Sharing data between threads classic race condition, and the use of a mutex internally to protect the stack contents doesn’t prevent it; it’s a consequence of the interface.
3 The common advice for avoiding deadlock is to always lock the two mutexes in the same order: if you always lock mutex A before mutex B, then you’ll never deadlock.
4 Deadlock doesn’t just occur with locks, although that’s the most frequent cause; you can create deadlock with two threads and no locks just by having each thread call
join() on the std::thread object for the other.