C# C语言中可变变量的赋值#

C# C语言中可变变量的赋值#,c#,volatile,C#,Volatile,我对C#的理解是(感谢杰夫·里克特和乔恩·斯基特)任务是“原子的”。但当我们混合读写(递增/递减)时,情况就不一样了,因此我们需要在互锁系统上使用方法。如果只进行读取和分配,这两个操作是否都是原子操作 public class Xyz { private volatile int _lastValue; private IList<int> AvailableValues { get; set; } private object syncRoot = new

我对C#的理解是(感谢杰夫·里克特和乔恩·斯基特)任务是“原子的”。但当我们混合读写(递增/递减)时,情况就不一样了,因此我们需要在互锁系统上使用方法。如果只进行读取和分配,这两个操作是否都是原子操作

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;

    }
公共类Xyz
{
私有volatile int_lastValue;
私有IList可用值{get;set;}
私有对象syncRoot=新对象();
私有随机=新随机();
//可由多个线程访问
public int GetNextValue()//并在存储耗尽后返回最后一个值
{
//...
var计数=0;
var返回值=0;
锁定(同步根)
{
计数=可用值。计数;
}
如果(计数=0)
{
//读取…但不锁定…潜在的多次读取
returnValue=\u lastValue;
}
其他的
{
var toReturn=random.Next(0,计数);
锁定(同步根)
{
returnValue=可用值[toReturn];
可用值。移除(返回);
}
//可能多次写入…最后一个写入程序获胜
_lastValue=返回值;
}
返回值;
}

此(原子性)不保证。

使用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;
    }
}
公共类Xyz
{
私有volatile int_lastValue;
私有IList可用值{get;set;}
私有对象syncRoot=新对象();
private Random rand=new Random();
//可由多个线程访问
public int GetNextValue()//并在存储耗尽后返回最后一个值
{
//...
锁定(同步根)
{
var count=AvailableValues.count;
如果(计数=0)
返回_lastValue;
toReturn=rand.Next(0,计数);
_lastValue=可用值[toReturn];
可用值。移除(返回);
}
返回_lastValue;
}
}

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

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

这里有两个“原子”的含义:

  • 是一个单读原子本身/单写原子本身(即,另一个线程是否可以得到两个
    Double
    s的两个不同部分,从而产生一个从未实际存在过的数字)
  • 读/写对是原子的还是隔离在一起的
“本身”取决于值的大小;它可以在单个操作中更新吗?读/写对更多地与隔离有关-即防止丢失更新


在您的示例中,两个线程可以读取相同的
\u lastValue
,两个线程都进行计算,然后(分别)更新
\u lastValue
。其中一个更新将丢失。事实上,我希望您希望在读/写过程的期间
锁定

它们适用于某些类型。在您的情况下,它是int,因此根据C规范它是原子的。但与本主题中的其他人一样,它并不保证您的代码是thread safe。

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

3.5和以上,考虑一下,微软是这样描述的:

ReaderWriterLockSlim与ReaderWriterLockSlim类似,但它简化了递归规则以及升级和降级锁状态的规则。ReaderWriterLockSlim避免了许多潜在死锁的情况。此外,ReaderWriterLockSlim的性能明显优于ReaderWriterLockSlim。建议所有人使用ReaderWriterLockSlim新的发展

我对C#的理解是(感谢 杰夫·里克特和乔恩·斯基特)那 分配是“原子的”

赋值通常不是原子的。C#规范仔细地指出了保证是原子的内容。参见第5.5节:

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

(重点加上。)

如果只有读取和分配,则两者都将 操作是原子的吗

第5.5节再次回答了您的问题:


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

+1我的理解是,IA64重新排序指令的方式存在一些怪癖,这在x64 x32上的volatile上很可能不会发生-就像我说的“变得复杂”-p幸运的是,我没有看到太多IA64…@Marc:是的,我的意思是原子,就像在你的def#1中一样。值丢失不是问题(在这种特殊情况下),因为我只想保留最后一个值……在修订后的代码中,我们只有在存储耗尽后才读取操作。
int
是有保证的