Assignment to volatile variable in C#

ⅰ亾dé卋堺 提交于 2019-12-10 18:19:50

问题


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;

    }


回答1:


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.




回答2:


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




回答3:


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.




回答4:


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.




回答5:


This (atomicity) is NOT guaranteed.




回答6:


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.



来源:https://stackoverflow.com/questions/1097762/assignment-to-volatile-variable-in-c-sharp

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!