Search code examples
c++atomicmodulostdatomic

Is it atomic operation when exchange std::atomic with itself?


Will following code be executed atomically?

const int oldId = id.exchange((id.load()+1) % maxId);

Where id is std::atomic<int>, and maxId is some integer value.

I searched google and stackoverflow for std::atomic modulo increment. And I found some topics but I can't find clear answer how to do that properly.

In my case even better would be to use:

const int newId = id.exchange((++id) % maxId);

But I am still not sure if it will be executed atomically.


Solution

  • No, this is not atomic, because the load() and the exchange() are separate operations, and nothing is preventing id from getting updated after the load, but before the exchange. In that case your exchange would write a value that has been calculated based on a stale input, so you end up with a missed update.

    You can implement a modulo increment using a simple compare_exchange loop:

    int val = id.load();
    int newVal = (val + 1) % maxId;
    while (!id.compare_exchange_weak(val, newVal) {
      newVal = (val + 1) % maxId;
    }
    

    If the compare_exchange fails it performs a reload and populates val with the updated value. So we can re-calculate newVal and try again.

    Edit:

    The whole point of the compare-exchange-loop is to handle the case that between the load and the compare-exchange somebody might change id. The idea is to:

    1. load the current value of id
    2. calculate the new value
    3. update id with our own value if and only if the value currently stored in id is the same one as we read in 1. If this is the case we are done, otherwise we restart at 1.

    compare_exchange is allows us to perform the comparison and the conditional update in one atomic operation. The first argument to compare_exchange is the expected value (the one we use in our comparison). This value is passed by reference. So when the comparison fails, compare_exchange automatically reloads the current value and updates the provided variable (in our case val).

    And since Peter Cordes pointed out correctly that this can be done in a do-while loop to avoid the code duplication, here it is:

    int val = id.load();
    int newVal;
    do {
      newVal = (val + 1) % maxId;
    } while (!id.compare_exchange_weak(val, newVal);