Search code examples
c++lockingatomicspinlock

Are the memory barriers correct for this lock?


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.


Solution

  • 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.