The following is not intended to be a complete treatise on atomics, but just an answer to a specific question.
A colleague asked:
How should one write the following “conditional interlocked” function in the new C++ atomic<> style?
// if (*plValue >= 0) *plValue += lAdd ; return the original value LONG MpInterlockedAddNonNegative(__inout LONG volatile* plValue, __in LONG const lAdd) { LONG lValue = 0; for (;;) { lValue = *plValue; // volatile plValue suppress compile optimizations in which
// lValue is optimized out hence MT correctness is broken if (lValue < 0) break; if (lValue == InterlockedCompareExchange(plValue, lValue + lAdd, lValue)) { break; } } return lValue; }
Note: ISO C/C++ volatile is not for inter-thread communication,[*] but this is legacy code that predates std::atomics and was using a combination of platform-specific volatile semantics and Windows InterlockedXxx APIs.
The answer is to use a CAS loop (see code at top), which for std::atomics is spelled compare_exchange:
- Use compare_exchange_weak by default when looping on this which generally naturally tolerates spurious failures.
- Use compare_exchange_strong for single tests when you generally don’t want spurious failures.
- Usage note: In the code at top we save an explicit reload from ‘a’ in the loop because compare_exchange helpfully (or “helpfully” – this took me a while to discover and remember) stores the actual value in the ‘expected’ value slot on failure. This actually makes loops simpler, though some of us are still have different feelings on different days about whether this subtlety was a good idea… anyway, it’s in the standard.
For the std::atomic version, roughly (compiling in my head), and generalizing to any numeric type just because I’m in the habit, and renaming for symmetry with atomic<T>::fetch_add(), I think this is what you want:
template<typename T> T fetch_add_if_nonnegative( std::atomic<T>& a, T val ) { T old = a; while( old >= 0 && !a.compare_exchange_weak( old, old+val ) ) { } return old; }
Because the only test in your loop was to break on negative values, it naturally migrated into the loop condition. If you want to do more work, then follow the general pattern which is the following (pasting from the standard, 29.6.5/23 – and note that the explicit “.load()” is unnecessary but some people including the author of this clause of the standard prefer to be pedantically explicit :) ):
[ Example: the expected use of the compare-and-exchange operations is as follows.
The compare-and-exchange operations will update expected when another iteration of the loop is needed.
expected = current.load();
do {
desired = function(expected);
} while (!current.compare_exchange_weak(expected, desired));
—end example ]
So the direct implementation of your function in the general pattern would be:
T old = a; do { if( old < 0 ) break; } while(!a.compare_exchange_weak( old, old+val ) )
but since that easily moves into the loop test I just did this instead in the code at top:
T old = a; while( old >= 0 && !a.compare_exchange_weak( old, old+val ) ) { }
and hoping that no one will discover and point out that I’ve somehow written a subtle bug by trying to make the code cuter just before leaving for a holiday weekend.
[*] Here’s the difference between ISO C/C++ volatile vs. std::atomic<T>/atomic_T: ISO C/C++ volatile is intended to be used only for things like hardware access and setjmp/longjmp safety, to express that the variable is in storage that is not guaranteed to follow the C++11 memory model (e.g., the compiler can’t make any assumptions about it). It has nothing to do with inter-thread communication – the proper tool for that is std::atomic<T> which for C compatibility can also be spelled atomic_T (note that in Java and C# this is called volatile which adds to the confusion). For more, see my article “volatile vs. volatile” and Hans Boehm’s ISO C++ paper “Should volatile Acquire Atomicity and Thread Visibility Semantics?”.