Search code examples
c++multithreadingatomicstdatomic

std::atomic<bool> execution guarantee?


I know std::atomics are supposed to have well defined behavior, but I can't find an easy-to-digest online answer to this question: Do std::atomic.load() and .store() have execution guarantees?

If two threads attempt a concurrent write or read on the same std::atomic object, are both the write and read guaranteed to be executed? In other words, is it possible either the write or read task will simply not get done? Will one or both be blocked? Or are they guaranteed to be sequentialized? I am NOT asking here about order of operations. I am asking simply if the operation will be done at some unspecified time in the future.


Solution

  • It is a basic assumption that the compiler and processor ensures that the programmed operations are executed. This has nothing to do with std::atomic<>. The guarantee which std::atomic<> offers is that single operations happen atomically.

    So what does that mean?

    Consider two threads, A and B, which both increment the same integer variable. This operation typically involves reading the integer, adding 1 and writing the result (notice that this is only an example, the C++ standard does not say anything about how operations are broken into atomic steps and thus we cannot make any assumptions about it based on the standard).

    In this case we would have the steps "read", "add one" and "write". For each thread, these steps are guaranteed to the executed in that order, but there is no guarantee how the steps are interleaved. It may be:

       B: read
       B: add1
       B: write
       A: read
       A: add1
       A: write
    

    which results in the integer being incremented twice.

    It could also be

       A: read
       A: add1
       B: read
       B: add1
       B: write
       A: write
    

    which would result in the integer being incremented only once.

    Thus this implementation would have a race condition.

    To get rid of that we can use a std::atomic<int> instead of a plain int for the integer. std::atomic<int> implements the ++ operator and the guarantee which std::atomic<> provides is that incrementing in this case will happen atomically.

    In the example, this means that the sequence of steps - read, add one, write - will not be interrupted by another thread. There is still no guarantee about the order of execution between the threads. Hence, we could have

       A: read
       A: add1
       A: write
       B: read
       B: add1
       B: write
    

    or

       B: read
       B: add1
       B: write
       A: read
       A: add1
       A: write
    

    but other combinations will not be possible and in both cases the integer will be incremented twice. Thus there is no race condition.