Possible Duplicate:
Interlocked.CompareExchange<Int> using GreaterThan or LessThan instead of equality
I know that Interlocked.CompareExchange exchanges values only if the value and the comparand are equal,
How to exchange them if they are not equal to achieve something like this ?
if (Interlocked.CompareExchange(ref count, count + 1, max) != max)
// i want it to increment as long as it is not equal to max
{
//count should equal to count + 1
}
A more efficient (less bus locking and less reads) and simplified implementation of what Marc posted:
If you have really high contention, we might be able to improve scalability further by reducing the common case to a single atomic increment instruction: same overhead as CompareExchange, but no chance of a loop.
Here we allow
countto go up todriftvalues beyondmax. But we still return only up tomax. This allows us to fold the entire op into a single atomic increment in most cases, which will allow maximum scalability. We only need more than one op if we go above ourdriftvalue, which you can probably make large enough to make very rare.In response to Marc’s worries about Interlocked and non-Interlocked memory access working together:
Regarding specifically
volatilevs Interlocked:volatileis just a normal memory op, but one that isn’t optimized away and one that isn’t reordered with regards to other memory ops. This specific issue doesn’t revolve around either of those specific properties, so really we’re talking non-Interlocked vs Interlocked interoperability.The .NET memory model guarantees reads and writes of basic integer types (up to the machine’s native word size) and references are atomic. The Interlocked methods are also atomic. Because .NET only has one definition of “atomic”, they don’t need to explicitly special-case saying they’re compatible with each-other.
One thing
Volatile.Readdoes not guarantee is visibility: you’ll always get a load instruction, but the CPU might read an old value from its local cache instead of a new value just put in memory by a different CPU. x86 doesn’t need to worry about this in most cases (special instructions likeMOVNTPSbeing the exception), but it’s a very possible thing with others architectures.To summarize, this describes two problems that could affect the
Volatile.Read: first, we could be running on a 16-bit CPU, in which case reading anintis not going to be atomic and what we read might not be the value someone else is writing. Second, even if it is atomic, we might be reading an old value due to visibility.But affecting
Volatile.Readdoes not mean they affect the algorithm as a whole, which is perfectly secure from these.The first case would only bite us if you’re writing to
countconcurrently in a non-atomic way. This is because what could end up happening is (write A[0]; CAS A[0:1]; write A[1]). Because all of our writes tocounthappen in the guaranteed-atomic CAS, this isn’t a problem. When we’re just reading, if we read a wrong value, it’ll be caught in the upcoming CAS.If you think about it, the second case is actually just a specialization of the normal case where the value changes between read and write — the read just happens before we ask for it. In this case the first
Interlocked.CompareExchangecall would report a different value than whatVolatile.Readgave, and you’d start looping until it succeeded.If you’d like, you can think of the
Volatile.Readas a pure optimization for cases of low contention. We could initoldvalwith0and it would still work just fine. UsingVolatile.Readgives it a high chance to only perform one CAS (which, as instructions go, is quite expensive especially in multi-CPU configurations) instead of two.But yes, as Marc says — sometimes locks are just simpler!