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.
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:
id
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);