Search code examples
c++concurrencyatomicstdatomic

Does memory_order_seq_cst store guarantee that memory_order_relaxed load will read written value?


Is in the following code x_value guaranteed to be 42?

std::atomic<int> x;

Thread A:
  x.store(42, std::memory_order_seq_cst);
  wakeThreadB();

Thread B:
  int x_value = x.load(std::memory_order_relaxed);
  assert(x_value == 42);

I have tried to test it, and it seemed that thread B always reads correct value. But I'm not sure if it is guaranteed.


Solution

  • A relaxed load does not synchronize with any other load/store before or after it.

    Note also that memory order semantics are about the visibility of other work done in relation to the syncronization variable.

    So e.g. the following wouldn't be correct:

    std::atomic<int> x;
    int y;
    
    void func_A()
    {
      y = 1;
      x.store(42, std::memory_order_seq_cst); // "releases" y = 1
    }
    
    void func_B()
    {
      while (x.load(std::memory_order_relaxed) != 42) {} // does NOT "acquire" anything
      assert(y == 1); // not guaranteed
    }
    
    int main()
    {
      std::thread a(&func_A), b(&func_B);
      a.join();
      b.join();
    }
    

    Mandatory note here: "it always works on my machine" doesn't make it correct; without synchronization it's a data race, a form of undefined behavior.

    But in your specific case, if by wakeThreadB() you mean construction of std::thread instance with Thread B code as the thread function, then the code is actually correct - std::thread creation is a syncronization event (see [thread.thread.constr]/5), so any load in Thread B is guaranteed to see everything done before Thread B was started.

    That means that the atomic store of x does not matter at all, the code would be correct even with a non-atomic int:

    void func_B();
    
    int x;
    std::thread t;
    
    void func_A()
    {
      x = 42;
      t = std::thread(&func_B); // "releases" x = 42
    }
    
    void func_B() // "acquires" x = 42
    {
      assert(x == 42); // guaranteed success
    }
    
    int main()
    {
      func_A();
      t.join();
    }
    

    Similarly, std::condition_variable uses a mutex internally and a mutex release/lock is a synchronization event (see [thread.mutex.requirements.mutex]/25), so notifying another thread via a condition_variable would also work correctly without the need for any atomics.