According to Antony Williams' book C++ Concurrency in Action a spinlock can be implemented as follows:
class spinlock_mutex {
std::atomic_flag flag;
public:
spinlock_mutex() : flag(ATOMIC_FLAG_INIT) {}
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) ;
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
If I understand correctly, the memory_order_acquire
tag ensures visibility of the most recent store operation that used the memory_order_release
tag. ("A release operation synchronizes-with an acquire operation.")
Since test_and_set
is also a store operation I would expect that a memory_order_acq_rel
tag is required inside the lock()
method in order to ensure visibility of the locked state in other threads that attempt to lock the mutex.
Why is memory_order_acquire
sufficient?
memory_order_acquire: Syncs reading this atomic variable AND makes sure relaxed vars written before this are synced as well. (does this mean all atomic variables on all threads are synced?) memory_order_release: Pushes the atomic store to other threads (but only if they read the var with consume/acquire)
An operation has acquire semantics if other processors will always see its effect before any subsequent operation's effect. An operation has release semantics if other processors will see every preceding operation's effect before the effect of the operation itself.
The acquire and release are related not to this atomic variable, but to the rest of variables read/written in each one of the threads.
To make it more obvious, without the release in unlock()
and the acquire in the lock()
the flag would still be visible. The problem is that a write to a variable protected by the spin lock may be pushed after the lock is released, introducing a race condition. In the same way a read of that variable inside the lock might be moved to before the lock()
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