Search code examples
c++memory-barriers

Replicating a race condition with memory_order_relaxed


void experiment_relaxed() {
   atomic<int> x;
   atomic<int> y;

   auto write = [&x, &y]() {
      y.store(10, memory_order_relaxed);
      x.store(1, memory_order_relaxed);
   };

   auto read = [&x, &y]() {
      if (x.load(memory_order_relaxed) == 1) {
         assert(y.load(memory_order_relaxed) == 10);
      }
   };

   auto reader = thread{read};
   auto writer = thread{write};

   reader.join();
   writer.join();
}

I'm expecting that since x and y are both atomic variables and using memory_order_relaxed, the order of writes should be arbitrary. But the assert is not failing even after running this function 100,000+ times. What could be forcing the ordering?

This experiment is somewhat based on https://gcc.gnu.org/wiki/Atomic/GCCMM/AtomicSync

Compiled it with

g++ -O3 -std=c++20 test.cpp -o test

Solution

  • From the specification point of view, the assert failing and the assert succeeding are both permissible behaviors of the program.

    But that doesn't at all mean that you can expect to see both behaviors with some probability. Instead, you simply have no guarantee at all which behavior you will observe.

    Practically speaking this mostly depends on whether or not the compiler reorders the two stores in write when translating them to stores in the target architecture. On some architectures reordering by the CPU may also be relevant, but not e.g. on x86.

    You can't verify that a program is free of race conditions, data races or unspecified/undefined behavior in general only by running (or compiling) it often.