c++multithreadingmutexatomicspinlock

Why does this spinlock require memory_order_acquire_release instead of just acquire?


// spinlockAcquireRelease.cpp

#include <atomic>
#include <thread>

class Spinlock{
  std::atomic_flag flag;
public:
  Spinlock(): flag(ATOMIC_FLAG_INIT) {}

  void lock(){
    while(flag.test_and_set(std::memory_order_acquire) ); // line 12
  }

  void unlock(){
    flag.clear(std::memory_order_release);
  }
};

Spinlock spin;

void workOnResource(){
  spin.lock();
  // shared resource
  spin.unlock();
}


int main(){

  std::thread t(workOnResource);
  std::thread t2(workOnResource);

  t.join();
  t2.join();

}

In the notes, it is said:

In case more than two threads use the spinlock, the acquire semantic of the lock method is not sufficient. Now the lock method is an acquire-release operation. So the memory model in line 12 [the call to flag.test_and_set(std::memory_order_acquire)] has to be changed to std::memory_order_acq_rel.

Why does this spinlock work with 2 threads but not with more than 2? What is an example code that cause this spinlock to become wrong?

Source: https://www.modernescpp.com/index.php/acquire-release-semantic


Solution

  • std::memory_order_acq_rel is not required.

    Mutex synchronization is between 2 threads.. one releasing the data and another acquiring it.
    As such, it is irrelevant for other threads to perform a release or acquire operation.

    Perhaps it is more intuitive (and efficient) if the acquire is handled by a standalone fence:

    void lock(){
      while(flag.test_and_set(std::memory_order_relaxed) )
        ;
      std::atomic_thread_fence(std::memory_order_acquire);
    }
    
    void unlock(){
      flag.clear(std::memory_order_release);
    }
    

    Multiple threads can spin on flag.test_and_set, but one manages to read the updated value and set it again (in a single operation).. only that thread acquires the protected data after the while-loop.