Is this correct? And am I correct in assuming that applying the memory ordering on the std::atomic_flag
does NOT provide synchronization for general-purpose locks?
#include <atomic>
class Spinlock
{
public:
Spinlock(): f(ATOMIC_FLAG_INIT) {}
void lock()
{
while(f.test_and_set(std::memory_order_relaxed));
std::atomic_thread_fence(std::memory_order_acquire);
}
void unlock()
{
std::atomic_thread_fence(std::memory_order_release);
f.clear(std::memory_order_relaxed);
}
private:
std::atomic_flag f;
};
I'm sorry if this is a stupid question, but I feel like an std::atmoic_thread_fence
IS necessary for a general-purpose lock, and that applying memory_order_acquire
on the test_and_set
and memory_order_release
on the clear
is not enough, but I'm also not sure.
The usual pattern is to use test_and_set(memory_order_acquire)
and clear(memory_order_release)
. But I suspect you know that already.
According to the standard section 29.8 [atomic.fences] (2):
A release fence A synchronizes with an acquire fence B if there exist atomic operations X and Y, both operating on some atomic object M, such that A is sequenced before X, X modifies M, Y is sequenced before B, and Y reads the value written by X or a value written by any side effect in the hypothetical release sequence X would head if it were a release operation.
In your code, A is the fence in your unlock()
function; X is the clear()
; Y is the fence in your lock()
function; and B is the test_and_set()
. So your code meets the requirements of this section of the standard and therefore your unlock()
and lock()
functions are properly synchronized.