3

I was trying to consolidate a bunch of repeat code into a utility class with the following structure:

public class Shared<T>
{
    private volatile T _value;

    public object Lock { get; private set; }

    public T Value
    {
        get { lock (Lock) return _value; }
        set { lock (Lock) _value = value; }
    }

    public Shared(T startingValue)
    {
        _value = startingValue;
        Lock = new object();
    }

    public Shared()
        : this(default(T))
    {
    }
}

However, C# will not let me. The documentation indicates that T must be a reference type or one of the primitives (int, bool, etc.). The only types I care about are reference types and bools. Obviously, I cannot use an unbounded type T. Is there a way I can make this work? Is it even possible to add constraints to this to make it work as a Shared<bool>? I'm not even allowed to mark other struct types volatile. :(

If there is no possible way to mark it volatile, do I have other assurances that it will not be optimized away when multiple threads are trying to read this value?

TheBuzzSaw
  • 8,648
  • 5
  • 39
  • 58

2 Answers2

5

You can easily constraint the generic type to just references...

public class Shared<T> where T : class

...but there is no way to restrict this to a bool as well. The class is very small so you could just create a specific implementation for the Boolean type.

BUT...

Your locking during the read and write of the value is completely useless. The read/write of an object reference is atomic within .NET and so there is no need for the lock. It just slows it down. (Boolean is implemented as a 4 byte integer and they are atomic as well, so it is also not needed for your Boolean version)

The volatile keyword will mean that every time the value is read it will ensure it reads the value again from memory. This gets around the issue where the value might be cached in a register or in the L1/L2 cache. In which case subsequent accesses will be the register/cached value even though another CPU might have changed it in main memory. So your volatile will prevent the value being cached in a register and ensure that each read is consistent with main memory.

Phil Wright
  • 22,580
  • 14
  • 83
  • 137
  • If I drop `volatile`, I can go on using `Shared`, which would make me very happy. – TheBuzzSaw Mar 04 '15 at 23:27
  • 1
    per https://msdn.microsoft.com/en-us/library/x13ttww7.aspx, volatile is an instruction to the compiler. It does nothing at runtime. It just prevents the compiler from turning it into a hard value because it thinks you don't change it anywhere. – Cdaragorn Mar 04 '15 at 23:43
  • Thanks for the link. As the link says, 'This ensures that the most up-to-date value is present in the field at all times.'. – Phil Wright Mar 04 '15 at 23:48
  • My point is simply that this has nothing to do with how the hardware manages the caches. The CPU never changes a value in main memory. It must always pull that value into CPU cache to change it. The CPU cache always contains the truth; main memory is never used if the value is already in CPU cache. Volatile just tells the compiler not to optimize this value. – Cdaragorn Mar 05 '15 at 15:43
  • "Your locking during the read and write of the value is completely useless." -- I am aware of the atomic nature of these types, but that does not make my lock useless. I exposed the lock publicly. The whole point is that _other_ code can lock the object, thus preventing the value from being changed or viewed the entire time that lock is being held. – TheBuzzSaw Mar 05 '15 at 15:47
1

(This is not specific to the volatile problem, but a possible general workaround for such cases.)

You can make your class implement an interface (say, IShared<T>). Construct instances of it through a generic factory method. You can then provide multiple implementations.

For reference types, you can return instances of the class you already have, with a constraint of T : class. For primitive types, you can return instances of specialized non-generic classes that implement IShared<bool>, IShared<int>, etc.

Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104
  • This answer better falls in line with what I was trying to accomplish. While I may end up not needing the `volatile` anyway, this would indeed address the issue. Having a factory method (and hiding the constructors) would allow me to create sub-classes to deal with C#'s resistance. – TheBuzzSaw Mar 05 '15 at 15:49