1

I need to get this code from .NET 4.5+ working on .NET 4.0. The generic Volatile.Read isn't available in that version. I'm trying to use the Thread.VolatileRead to get this working. I need to know if this outright a bad idea and if not, what I should test for?

See what I replaced in the commented line.

internal static TValue EnsureSingletonInitialized<TValue, TArg>(ref TValue value,
    TArg arg, Func<TArg, TValue> initialize)
    where TValue : class
{
    //TValue currentValue = Volatile.Read(ref value);

    object o = value;
    TValue currentValue = (TValue)Thread.VolatileRead(ref o);

    if (null != currentValue)
        return currentValue;

    TValue candidateValue = initialize(arg);

    return Interlocked.CompareExchange(ref value, candidateValue, null) ?? candidateValue;
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Rex NFX
  • 443
  • 5
  • 11

1 Answers1

1

The safest implementations of the missing Read<T>/Write<T> methods are probably these:

public static class VolatileEx
{
    public static T Read<T>(ref T location) where T : class
    {
        return Interlocked.CompareExchange(ref location, null, null);
    }

    public static void Write<T>(ref T location, T value) where T : class
    {
        Interlocked.Exchange(ref location, value);
    }
}

Using the Interlocked ensures that the Read/Write are atomic, and also that the preceding/following instructions will not be reordered. The Interlocked APIs impose full memory barriers, so they are more expensive than the Volatile APIs that impose half fences. But since you downgraded to an older .NET platform, losing some performance is to be expected.

Source code:

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 2
    `Thread.VolatileRead` should work; as a non-inline function it can't optimize away the load. It includes a full `MemoryBarrier();` so it's much less efficient than `Volatile.Read`, but doesn't dirty the cache line it's reading (which `lock cmpxchg` does). I assume C# guarantees that narrow-enough accesses are automatically atomic, since obviously the intent of `Thread.VolatileRead` is inter-thread communication, but the source just uses plain assignment on ordinary objects. – Peter Cordes Jun 14 '23 at 19:29
  • 1
    Using `xchg` for a store is equivalent (and more efficient) than `Thread.VolatileWrite` doing an assignment and then a separate `MemoryBarrier();`, at least on x86 ([Why does a std::atomic store with sequential consistency use XCHG?](https://stackoverflow.com/q/49107683)). Perhaps less efficient on other ISAs if they have a more efficient `MemoryBarrier()`. – Peter Cordes Jun 14 '23 at 19:38