在C#中赋值给volatile变量

时间:2009-07-08 12:29:53

标签: c# volatile

我对C#的理解说(感谢Jeff Richter和Jon Skeet),任务是“原子的”。什么不是当我们混合阅读&写入(递增/递减)因此我们需要在Interlocked上使用方法。如果只有Read& assign这两个操作都是原子的吗?

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;

    }

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; }

6 个答案:

答案 0 :(得分:18)

  

我对C#的理解说(感谢   杰夫里希特&amp; Jon Skeet)那个   赋值是“原子的”。

一般来说,赋值不是原子的。 C#规范小心地调出了保证原子的东西。见5.5节:

以下数据类型的读写是原子的:bool,char,byte,sbyte,short,ushort,uint,int,float和reference类型。此外,在先前列表中具有基础类型的枚举类型的读取和写入也是原子的。其他类型的读写,包括long,ulong,double和decimal,以及用户定义的类型,不保证是原子的

(强调补充。)

  

如果只有Read&amp;分配两者   这些操作是原子的?

同样,第5.5节回答了你的问题:

无法保证原子读取 - 修改 - 写入

答案 1 :(得分:11)

volatile实际上与缓存(寄存器等)更相关;使用volatile,您知道该值实际上是从内存中立即写入/读取 (实际上并非总是如此)。这允许不同的线程立即看到彼此的更新。指令重新排序还有其他微妙的问题,但这很复杂。

这里要考虑“原子”的两个含义:

  • 本身就是单个读原子/写原子本身(即另一个线程可以得到两个不同的两个Double的一半,产生一个从未存在的数字)
  • 是读/写对原子/隔离在一起

“自身”取决于价值的大小;可以在一次操作中更新吗?读/写对更多地与隔离有关 - 即防止丢失更新。

在您的示例中,两个线程可以读取相同的_lastValue,两者都进行计算,然后(单独)更新_lastValue。其中一个更新将丢失。实际上,我希望您希望在读/写过程的持续时间lock

答案 2 :(得分:5)

使用volatile关键字不会使访问线程安全,它只是确保从内存中读取变量,而不是从可能从先前读取缓存的寄存器中读取。某些体系结构会进行此优化,这会导致在多个线程写入同一变量的情况下使用过时值。

为了正确同步访问权限,您需要更广泛的锁定:

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;
    }
}

如果性能是一个问题,您可能需要考虑使用LinkedList for AvailableValues,因为它支持O(1)删除操作。

答案 3 :(得分:2)

对于.Net 2.0及之前的版本,有一个名为ReaderWriterLock的类,它允许您单独阻止写入和读取。可能会有帮助。

对于.Net 3.5及更高版本,请考虑ReaderWriterLockSlim,微软会这样描述;

  

ReaderWriterLockSlim类似于ReaderWriterLock,但它简化了递归规则以及升级和降级锁定状态的规则。 ReaderWriterLockSlim避免了许多潜在的死锁案例。此外,ReaderWriterLockSlim的性能明显优于ReaderWriterLock。建议将ReaderWriterLockSlim用于所有新开发。

答案 4 :(得分:0)

不保证这(原子性)。

答案 5 :(得分:0)

它们适用于某些类型link。在你的情况下,它是一个int,所以根据C#规范,它是原子的。但正如本主题中的其他人一样,它并不保证您的代码是线程安全的。