There are helpers like:
template <typename T>
bool TryCompareExchange(T volatile * p, T exchange, T* compare)
{
T old = Traits<T>::InterlockedCompareExchange(p, exchange, *compare);
bool success = (old == *compare);
if (!success) *compare = old;
return success;
}
Traits handles char, short, int, long __int64, unsigned, etc., and same-sized structs (hopefully with consistent/zeroed padding).
Leading to uses like:
void increment(long volatile * p)
{
long old = *a;
while (!TryCompareExchange(p, old + 1, &old)) ;
}The problem comes when used with InterlockedCompareExchange128.
There is no atomic get/set128.
They can be synthesized from InterlockedCompareExchange128.
There are at least two incorrect forms, which do work in non-race conditions, so lull us into complacency ("it works"):
template <typename T>T Get(T volatile * p){T value;while (!TryCompareExchange(p, *p, &value)) ; // incorrect codereturn value;}
*p is a non-atomic read.
If p happens to match input value, then the non-atomic *p will be written back.
This can be mitigated by initializing value to a never-set value.
If p happens to match input value, then the non-atomic *p will be written back.
This can be mitigated by initializing value to a never-set value.
But finding such a value is not really needed (keep reading).
template <typename T>void Set(T volatile * p, T value){TryCompareExchange(&value, value, p); // incorrect code
}
The write through the last parameter is not atomic.
The correct forms:
template <typename T>T Get(T volatile * p){T value;(void)TryCompareExchange(p, value, &value);return value;
}
If the compare does match, the same value will be written back, atomically.
template <typename T>
void Set(T volatile * p, T value)
{
T old = *p; // non-atomic read, ok
while (!TryCompareExchange(p, value, &old)) ;
}
Loop until the non-atomic read matches, upon which write the desired value atomically.