Unlocking a std::mutex
that wasn't locked is UB. Why is this so? Why doesn't it just have no effect, as the mutex isn't locked yet, or was already unlocked, so what's the harm of calling unlock again?
If a thread attempts to unlock a mutex that it has not locked or a mutex that is unlocked, an error is returned. If the mutex type is PTHREAD_MUTEX_RECURSIVE, the mutex maintains the concept of a lock count. When a thread successfully acquires a mutex for the first time, the lock count is set to one.
The mutex does not become unlocked until the owner has called pthread_mutex_unlock() for each successful lock request that it has outstanding on the mutex. An errorcheck mutex checks for deadlock conditions that occur when a thread relocks an already held mutex.
The thread that locks the mutex owns it, and the owning thread should be the only thread to unlock the mutex. If the mutex is destroyed while still in use, critical sections and shared data are no longer protected. This rule is a specific instance of CON31-C.
The std::scoped_lock and std::unique_lock objects automate some aspects of locking, because they are capable of automatically unlocking.
Unlocking std::mutex that wasn't locked is UB. Why is it so? Why isn't it just have no effect, as mutex isn't locked yet, or was already unlocked, so what's the harm of calling unlock again?
Because that would have a cost. That would require every implementation to contain the necessary internal checks to ensure this behavior.
If you want a mutex that has this kind of behavior, you can code one up. But then you will have to pay the costs of the extra checks to do this. But then the people who don't need this behavior won't pay those costs.
The costs would tend to be higher than you might think. Owning a mutex makes accessing everything protected by that mutex safe. If you don't own a mutex, it isn't safe to access things protected by that mutex. So on some implementations, that might require the equivalent of acquiring a mutex (so you can safely access the mutex's ownership data) before you could release the mutex. If the cost to acquire and release a mutex are comparable, this might double the cost of unlocking a mutex. Yuck.
It's historical.
It's possible to implement a mutex from semaphores. In this implementation, Unlocking increments a count, locking tests that it's not zero, then decrements it. (If memory serves, you need another semaphore to lock the "testing" bit - but I'll ignore that for this question). The only valid values for the mutex are 0 (locked) or 1 (unlocked).
By locking an already locked mutex, or by unlocking an unlocked mutex, you can drive the value outside the 0-1 range, making the mutex no longer perform correctly.
It's U/B, because it's possible to make mutexes that don't suffer from this issue, but not every system will have access to that kind of solution, so if you want portable C++ code, you have to have a mental-model of a simple count that increments/decrements, and only lock mutexes which or not locked. Only unlock mutexes that are locked.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With