multithreadingthread-safetyatomiclock-freestdatomic

Is branching on a std::atomic variable a possible source of bugs?


I heard that using if or switch on a std::atomic is a source of bugs, due to some arguments to do with memory ordering.

Specifically, code like:

std::atomic<bool> branchVar = false;
void secondThreadFunc() {
  puts("Starting 2nd thread");
  branchVar = true;
}

int main() {

  std::thread t(secondThreadFunc);
  Sleep(100);
  
  // Branching on a std::atomic considered dangerous due to memory ordering issues?
  if( branchVar ) {
    puts("Branch true");
  }
  else {
    puts("Branch false");
  }

  t.join();
}

Plants a race condition at the if(branchVar) call and is considered unsafe programming. A std::mutex should always be used to lock access of the branch variable instead

Is this true? Could someone explain to me why that is so? What about memory ordering would cause the std::atomic branch to cause a race condition?


Solution

  • There's nothing wrong with branching on atomic variables.

    It is common to have a loop that reads atomic variable, and exits if it reads as some value. It is called spinning. Spinning is basically a branch backwards on an atomic variable.

    Spinning is often used in implementation of synchronization primitives, including std::mutex or std::call_once. (You may not see it directly in the STL implementation, as this is usually done in a lower level).

    When you use atomic variable directly in if or switch expression, implicit conversion happens, which is equivalent to .load() without parameters, and the memory order is implicitly memory_order_seq_cst. It is the safest memory order, so will not introduce any new issues. The use of atomic may still be problematic, as there may be data races with other data, but it depends on your code, and doesn't have anything to do with the atomic itself.


    The advice to use std::mutex, etc, may come form the fact that std::atomic on its own provides atomicity only for itself. So if you modify atomic and some other data in a thread, and then branch on atomic, there may be a data race related to the other data modification. Or may be no data race, if you do everything right.