3 ms·
Rust doesn't solve this. You can easily make the same mistake as the article describes, in Rust. Thiez points out that a Rust mutex wraps the data it protects
by jlokier 3y ago
Rust doesn't solve this. You can easily make the same mistake as the article describes, in Rust.
Thiez points out that a Rust mutex wraps the data it protects. But that doesn't fix the issue described in the article. The C++ in the article also accesses all mutex-protected data safely. You could translate it almost line-for-line to Rust and still have the same race hazard.
It's because the race hazard is from wait-condition ordering, rather than mutex-protected data.
The article explains, it's tempting and intuitive to make kill_flag an atomic, outside the data protected by the mutex, because it's a sticky state, only written in one place.
I've seen this pattern used often enough in the real world, sometimes with the flag being a global variable. People do it. Shutdowns get stuck occasionally, nobody is sure why. Sometimes the flag is set in a signal handler, where it isn't safe to use mutex/notify operations anyway, and nobody thought hard enough about making those wakups reliable.
But those patterns lead to a race hazard with wait conditions. The article goes on to explain that kill_flag should be part of the data protected by the condition's mutex. And then there's no need to make it atomic.
After seeing that, it might be a surprise to find kill_flag doesn't have to be protected by the mutex after all!
That intuition that it can go outside was correct. It just needs a little ordering. The following code will also avoid the race hazard:
// Notify all workers that they need to die right now.
m_kill_flag = true;
// Acquire and release the mutex to ensure condvar waiters reliably see all state changes.
{ std::unique_lock<std::mutex> lock(m_mutex); }
// Wake all sleeping condvar waiters.
m_has_work.notify_all();
I would not recommend this for the situation in the article. But you can see how it generalises, for example you might have a global kill flag or memory statistic that's used for many different things, protected by different mutexes.
- lostmsu 3y agoIMHO, the best (in the sense of comprehensibility) fix to this is to never wait indefinitely. Waits should always have a timeout and on timeout all preconditions should be rechecked.