Implementation based on http://en.cppreference.com/w/cpp/atomic/atomic_flag
- Note
- - SpinLock - generally faster than most mutexes for short accesses (see kSpinLock_IsFasterThan_mutex) BUT it can be degererate (due to spinning) and take LONGER if used to hold locks a long time.
-
- not recursive mutex
-
Satisfies Concepts: o Common::StdCompat::BasicLockable<SpinLock> o Common::StdCompat::Lockable<SpinLock> o not movable<SpinLock>, not copyable<SpinLock> (as std::mutex)
-
Not Cancelation Point SpinLock contains no cancelation points, and so can be used interchangeably with mutex - just for the performance differences
-
Memory ordering You use a mutex to protect some data structure. A thread that acquires the lock needs to see the writes made by whoever held it last (that is the acquire), and a thread that releases the lock needs its own writes published to whoever acquires it next (that is the release). So acquire on the test_and_set () and release on the clear () is exactly - and entirely - what a mutex owes its caller.
That is not a preference, it is the contract: [thread.mutex.requirements.mutex] asks a mutex for just two things - that lock/unlock on a SINGLE mutex appear in one total order, which the modification order of the flag already provides at any memory order, and that "prior unlock () operations on the same
object shall synchronize with this operation", which is precisely release paired with acquire. Nothing weaker satisfies it and nothing stronger adds to it.
- See also
- try_lock ().
- Note
- v3.0d25 removed the BarrierType constructor argument (eNoBarrier / eReleaseAcquire / eMemoryTotalOrder), which had let a caller pick a different memory order for the flag. It went because neither alternative to the default was defensible: o eNoBarrier never had an observable effect - the flag was read acquire and cleared release whatever you passed - and had it been made to mean memory_order_relaxed it would have been actively unsafe. Mutual exclusion would survive (RMW atomicity does not depend on memory order), but the protected data would no longer be published to the next holder, and the compiler would be free to move that data's accesses across the lock and the unlock. It would not even have been faster on x86-64, where test_and_set () emits the same LOCK-prefixed xchg for relaxed as for acquire, and clear () the same plain store for relaxed as for release. o eMemoryTotalOrder (memory_order_seq_cst) is distinguishable only by protocols reasoning about the global order of operations on SEVERAL DIFFERENT locks. The standard deliberately promises a total order per mutex and not across mutexes, so that is not a mutex guarantee anywhere - offering it here would have made SpinLock advertise something std::mutex does not, against the whole premise that the two are interchangeable. Code that truly needs it wants a seq_cst atomic, named as such at the point of use.
-
References, in the order they became useful: o https://github.com/SophistSolutions/Stroika/issues/628 (STK-494) - where the reasoning above was worked out and settled (see the closing 2018-01-27 comment) o https://www.boost.org/doc/libs/1_54_0/doc/html/atomic/usage_examples.html#boost_atomic.usage_examples.example_spinlock o https://blog.regehr.org/archives/2173 - alleges that done properly you do not need the fence. That turned out to be right. It also suggests a fairer spinlock, which this still is not.
Definition at line 141 of file SpinLock.h.