You can safely increment and decrement std::atomic_int for example. But if you need to check for overflow or execute some routine conditinoally based on the value, then a lock is needed anyway. Since you must compare the value and the thread might be swapped off just after the comparison succeeded, another thread modifies, ... bug.
But if you need a lock then you can just use a plain integer instead of atomic. Am I right?
No, you can still use a std::atomic even conditionally.
Firstly, overflow behavior for .fetch_add is well defined as unsigned or 2's complement wrap-around for atomic types (although possibly not what you want). Unlike for non-atomic types where integer overflow is still UB.
You're right that foo++ and then a separate read of foo would be a bug, so use int tmp = foo++; to use the return value of the atomic RMW operation.
If you absolutely must check for overflow, or otherwise act conditionally, you can use compare-exchange. This lets you read the value, decide whether you want to do work on it and then atomically update the value back if it hasn't changed. And the key part here is the system will tell you if the atomic update failed, in which case you can go back to the start and read the new value and make the decision again.
As an example, if we only wanted to set the max value of an atomic integer to 4 (in some kind of refcounting, for instance), we could do:
#include <atomic>
static std::atomic<int> refcount = 0;
int val = refcount; // this doesn't need to be in the loop as on failure compare-exchange-strong updates it
while(true)
{
if(val == 4)
{
// there's already 4 refs here, maybe come back later?
break;
}
int toChangeTo = val + 1;
if(refcount.compare_exchange_weak(val, toChangeTo))
{
// we successfully took a ref!
break;
}
// if we fail here, another thread updated the value whilst we were running, just loop back and try again
}
Loops like this are often written as do{...}while(!refcount.compare_exchange_weak(...));
We use compare_exchange_weak instead of compare_exchange_strong. This can sometimes spuriously fail and so you need to do it in a loop. However, we have a loop anyway (and in general you always will as you need to handle real failures) and so compare_exchange_weak makes a lot of sense here.
On some ISAs, an asm loop is needed to implement compare_exchange_strong but not weak; on other ISAs like x86, both compile to the same asm. You're not making anything worse by using weak as long as the code to prepare for the next compare_exchange_weak attempt is cheap like in this case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With