Question

I have a generic field and a property that encapsulates it:

T item;

public T Item
{
    get { return item; }
    set { item = value; }
}

The problem is that this property can be written to from one thread and read from multiple threads at the same time. And if T is a struct, or long, readers might get results that are part old value and part new value. How can I prevent that?

I tried using volatile, but that's not possible:

A volatile field cannot be of the type 'T'.

Since this is a simpler case of code I've already written, which uses ConcurrentQueue<T>, I thought about using it here too:

ConcurrentQueue<T> item;

public T Item
{
    get
    {
        T result;
        item.TryPeek(out result);
        return item;
    }

    set
    {
        item.TryEnqueue(value);
        T ignored;
        item.TryDequeue(out ignored);
    }
}

This would work, but it seems to me that it's overcomplicated solution to something that should be simple.

Performance is important, so, if possible, locking should be avoided.

If a set happens at the same time as get, I don't care whether get returns the old value or the new value.

Was it helpful?

Solution

It completely depends on the type, T.

If you are able to place a class constraint on T then you don't need to do anything in this particular case. Reference assignments are atomic. This means that you can't have a partial or corrupted write to the underlying variable.

Same thing goes for reads. You won't be able to read a reference that is partially written.

If T is a struct, then only the following structures can be read/assigned atomically (according to section 12.5 of the C# specification, emphasis mine, also justifies the above statement):

Reads and writes of the following data types shall be atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list shall also be atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, need not be atomic. Aside from the library functions designed for that purpose, there is no guarantee of atomic read-modify-write, such as in the case of increment or decrement.

So if all you're doing is trying to read/write, and you meet one of the conditions above, then you don't have to do anything (but it means that you also have to place a constraint on the type T).

If you can't guarantee the constraint on T, then you'll have to resort to something like the lock statement to synchronize access (for reads and writes as mentioned before).

If you find that using the lock statement (really, the Monitor class) degrades performance, then you can use the SpinLock structure, as it's meant to help in places where Monitor is too heavy:

T item;

SpinLock sl = new SpinLock();

public T Item
{
    get 
    { 
        bool lockTaken = false;

        try
        {
            sl.Enter(ref lockTaken);
            return item; 
        }
        finally
        {
            if (lockTaken) sl.Exit();
        }
    }
    set 
    {
        bool lockTaken = false;

        try
        {
            sl.Enter(ref lockTaken);
            item = value;
        }
        finally
        {
            if (lockTaken) sl.Exit();
        }
    }
}

However, be careful, as the performance of SpinLock can degrade and will be the same as the Monitor class if the wait is too long; of course, given that you are using simple assignments/reads, it shouldn't take too long (unless you are using a structure which is just massive in size, due to copy semantics).

Of course, you should test this yourself for the situations that you predict that this class will be used and see which approach is best for you (lock or the SpinLock structure).

OTHER TIPS

I originally considered Interlocked, but I don't think it actually helps here as T isn't constrained to be a reference type. (If it were, the atomicity would be fine already.)

I would honestly start with locking - then measure performance. If the lock is uncontended, it should be really cheap. Only consider getting more esoteric when you've proved that the simplest solution is too slow.

Basically your expectation that this is simple fails due to the unconstrained genericity here - the most efficient implementation will differ based on the type.

Why do you need to protect this at all?

Changing the referenced instance of a variable is a atomic operation. So what ever you read with get won't be invalid. You can't tell if its the old or the new instance when set is running at the same time. But other then that you should be fine.

Partition I, Section 12.6.6 of the CLI spec states: "A conforming CLI shall guarantee that read and write access to properly aligned memory locations no larger than the native word size is atomic when all the write accesses to a location are the same size."

And as your variable is a reference type it always has the size of the native word. So your result is never invalid if you do something like this:

Private T _item;
public T Item
{
    get
    {
        return _item;
    }

    set
    {
        _item = value
    }
}

Example if you want to stick to the generic stuff and use it for everything. The approach is the use of a carrier helper class. It reduces the performance considerably but it will be lock free.

Public Foo
{
    Private Carrier<T> 
    {
        T _item
    }

    Private Carrier<T> _item;
    public T Item
    {
        get
        {
            Dim Carrier<T> carrier = _item;
            return carrier.item;
        }



set
    {
        Dim Carrier<T> carrier = new Carrier<T>();
        carrier.item = value;
        _item = carrier;
    }
}

}

This way you can ensure that you always use referenced types and your access is lock-free. Downside is that all set operations create garbage.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top