Search code examples
c#thread-safetyvolatilethread-synchronizationinterlocked

Difference between Interlocked.Exchange and Volatile.Write?


What is the difference between Interlocked.Exchange and Volatile.Write?

Both methods update value of some variable. Can someone summarize when to use each of them?

In particular I need to update double item of my array, and I want another thread to see the freshest value. What is preferred? Interlocked.Exchange(ref arr[3], myValue) or Volatile.Write(ref arr[3], info); where arr is declared as double?


Real example, I declare double array like that:

private double[] _cachedProduct;

In one thread I update it like that:

_cachedProduct[instrumentId] = calcValue;
//...
are.Set();

In another thread I read this array like that:

while(true)
{
    are.WaitOne();
    //...
    result += _cachedProduct[instrumentId];
    //...
}

For me it just works fine as is. However to make sure "it will always work" no matter what it seems I should add either Volatile.Write or Interlocked.Exchange. Because double update is not guaranteed to be atomic.

In the answer to this question I want to see detailed comparison of Volatile and Interlocked classes. Why we need 2 classes? Which one and when to use?


Another example, from the implementation of a locking mechanism in an in-production project:

private int _guard = 0;

public bool Acquire() => Interlocked.CompareExchange(ref _guard, 1, 0) == 0;

public void Release1() => Interlocked.Exchange(ref _guard, 0);
public void Release2() => Volatile.Write(ref _guard, 0);

Does it make any practical difference if the users of this API call the Release1 or the Release2 method?


Solution

  • the Interlocked.Exchange uses a processor instruction that guarantees an atomic operation.

    The Volatile.Write does the same but it also includes a memory barrier operation. I think Microsoft added Volatile.Write on DotNet 4.5 due to support of ARM processors on Windows 8. Intel and ARM processors differs on memory operation reordering.

    On Intel, you have a guarantee that memory access operations will be done in the same order they are issued, or at least that a write operation won't be reordered.

    From Intel® 64 and IA-32 Architectures Software Developer’s Manual, Chapter 8:

    8.2.2 Memory Ordering in P6 and More Recent Processor Families The Intel Core 2 Duo, Intel Atom, Intel Core Duo, Pentium 4, and P6 family processors also use a processor-ordered memory-ordering model that can be further defined as “write ordered with store-buffer forwarding.” This model can be characterized as follows.

    On ARM you don't have this kind of guarantee, so a memory barrier is required. An ARM blog explaining this can be found here: http://blogs.arm.com/software-enablement/594-memory-access-ordering-part-3-memory-access-ordering-in-the-arm-architecture/

    In your example, as the operation with double is not guaranteed to be atomic, I would recommend a lock to access it. Remember that you have to use the lock on both parts of your code, when reading and setting the value.

    A more complete example would be better to answer your question, as it is not clear what happens after these values are set. For a vector, if you have more readers than writers, consider the use of a ReaderWriterLockSlim object: http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx

    The number of threads and the frequency of read/writes can change dramatically your locking strategy.