Question

My understanding of C# says (thanks to Jeff Richter & Jon Skeet) that assignment is "atomic". What is not is when we mix reads & writes (increment/decrement) and hence we need to use methods on the Interlocked. If have only Read & assign would both the operations be atomic?

public class Xyz
{
    private volatile int _lastValue;
    private IList<int> AvailableValues { get; set; }
    private object syncRoot = new object();
    private Random random = new Random();

    //Accessible by multiple threads
    public int GetNextValue() //and return last value once store is exhausted
    {
        //...

        var count = 0;
        var returnValue = 0;

        lock (syncRoot)
        {
            count = AvailableValues.Count;
        }

        if (count == 0)
        {
            //Read... without locking... potential multiple reads
            returnValue = _lastValue;
        }
        else
        {

            var toReturn = random.Next(0, count);

            lock (syncRoot)
            {
                returnValue = AvailableValues[toReturn];
                AvailableValues.RemoveAt(toReturn);
            }
            //potential multiple writes... last writer wins
            _lastValue = returnValue;
         }

        return returnValue;

    }

Was it helpful?

Solution

volatile is actually more related to caching (in registers etc); with volatile you know that that value is actually written-to/read-from memory immediately (which isn't actually always the case otherwise). This allows different threads to immediately see updates from each other. There are other subtle issues with instruction re-ordering, but that gets complex.

There are two meanings of "atomic" to consider here:

  • is a single read atomic by itself / write atomic by itself (i.e. could another thread get two different halves of two Doubles, yielding a number that never actually existed)
  • is a read/write pair atomic/isolated together

The "by itself" depends on the size of the value; can it be updated in a single operation? The read/write pair is more to do with isolation - i.e. preventing lost updates.

In your example, it is possible for two threads to read the same _lastValue, both do the calculations, and then (separately) update _lastValue. One of those updates is going to get lost. In reality, I expect you want a lock over the duration of the read/write process.

OTHER TIPS

My understanding of C# says (thanks to Jeff Richter & Jon Skeet) that assignment is "atomic".

Assignment is not atomic in general. The C# specification carefully calls out what is guaranteed to be atomic. See section 5.5:

Reads and writes of the following data types are 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 are also atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, are not guaranteed to be atomic.

(Emphasis added.)

If have only Read & assign would both the operations be atomic?

Again, section 5.5 answers your question:

there is no guarantee of atomic read-modify-write

Using the volatile keyword does not make access thread-safe, it just makes sure that reads of the variable are read from memory instead of possibly being read from a register where it is cached from a previous read. Certain architectures make this optimisation which can lead to stale values being used where you have multiple threads writing to the same variable.

In order to synchronize access properly, you'll need to have a wider lock:

public class Xyz
{
    private volatile int _lastValue;
    private IList<int> AvailableValues { get; set; }
    private object syncRoot = new object();
    private Random rand = new Random();

    //Accessible by multiple threads
    public int GetNextValue() //and return last value once store is exhausted
    {
        //...

        lock (syncRoot)
        {
            var count = AvailableValues.Count;
            if(count == 0)
                return _lastValue;

            toReturn = rand.Next(0, count);
            _lastValue = AvailableValues[toReturn];
            AvailableValues.RemoveAt(toReturn);
        }
        return _lastValue;
    }
}

If performance is a concern you might want to consider using a LinkedList for AvailableValues, since it supports an O(1) remove operation.

For .Net 2.0 and before, there is a class called ReaderWriterLock which allows you to block writes and reads separately. Might be helpful.

For .Net 3.5 and above, consider ReaderWriterLockSlim, which microsoft describe like this;

ReaderWriterLockSlim is similar to ReaderWriterLock, but it has simplified rules for recursion and for upgrading and downgrading lock state. ReaderWriterLockSlim avoids many cases of potential deadlock. In addition, the performance of ReaderWriterLockSlim is significantly better than ReaderWriterLock. ReaderWriterLockSlim is recommended for all new development.

This (atomicity) is NOT guaranteed.

They are for some types link. In you case it is a int so it is atomic according to C# specs. But as others in this topic, it does not guarantee you code is thread-safe.

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