C# 如何在不锁定的情况下使读取此实例原语线程安全?

C# 如何在不锁定的情况下使读取此实例原语线程安全?,c#,.net,multithreading,.net-4.0,thread-safety,C#,.net,Multithreading,.net 4.0,Thread Safety,下面的类的问题是在读取myThreadSafe.Value时,它可能不会返回最新的值 public class ThreadSafe { private int value; public int Value { get { return value; } } public void Update() { Interlocked.Add(ref value, 47); // UPDATE: use interlocked to not di

下面的类的问题是在读取myThreadSafe.Value时,它可能不会返回最新的值

public class ThreadSafe
{   
    private int value;
    public int Value { get { return value; } }

    public void Update()
    {
        Interlocked.Add(ref value, 47); // UPDATE: use interlocked to not distract from the question being asked.
    }
}
我意识到在读和写的时候我可以锁定它:

public int Value { get { lock(locker) return value; } }

public void Update()
{
    lock(locker)
    {
        value += 47;        
    }
}
我一直遵循这种使用锁的模式。但是,我正在尝试减少代码中的锁的数量(有很多锁,而且它们经常被调用,我已经分析了
Montior.Enter()
占用的时间比我希望的要多,因为它被调用了很多次)

更新:我现在想知道锁是否真的会对确保我读取最新值产生任何影响,它仍然可能来自机器的一个CPU缓存,不是吗?(所有锁保证都是互斥线程访问)

我认为
volatile
将是答案,但确实说:“这确保字段中始终存在最新的值”,然而,当使用
volatile
时,我读写再读的CPU指令仍然可以交换,在这种情况下,我可以为
myThreadSafe.value获得一个以前的值,也许我可以接受这个值-只需要一次更新就可以了

对于
myThreadSafe.value
,获取最新值的最有效方法是什么

更新:此代码将在CPU架构上编译和运行:

  • x86
  • AMD64(尽管我可以构建为x86)
  • PowerPC
  • ARM(仅限Little endian)
使用运行时:

  • CLR v4.0
  • Mono(我不确定Mono运行时的版本,但它们是否对应于Mono版本:至少3.0)

我希望对所有版本使用相同的代码

我不确定你所说的“最新价值”是什么意思。您可以使用锁来确保在写入
值的同时不读取该值,这可能会产生一些奇怪的情况,但如果先读取然后再写入,则不会获得最新的值

public class ThreadSafe
{   
    private int value;
    public int Value { get { return value; } }

    public void Update()
    {
        Interlocked.Add(ref value, 47); // UPDATE: use interlocked to not distract from the question being asked.
    }
}
要处理我提到的奇怪之处,您可以像以前一样使用锁。但你似乎想要一个不同的解决方案。如果您不想锁定读取,但希望确保写入是原子的,这样在多线程写入期间执行读取时,读取不会返回奇数或其他一些混乱的事情,那么我建议使用
Interlocked

简单地说:

Interlocked.Add(ref value, 47);
更多的
联锁
功能可在


使用基本体时,这些函数非常有用。对于更复杂的对象,还需要其他解决方案,如ReaderWriterLockSlim和其他解决方案。

好的,我相信我找到了答案,我的担心得到了证实

该代码在x86和AMD64上是线程安全的,因为当变量写入时,它们会使CPU缓存失效,从而导致后续读取从内存中读取变量。引用Shafqay Ahmed Jeffrey Richter的话:

由于两个处理器可以具有不同的缓存(即ram的副本),因此它们可以具有不同的值。在x86和x64中,处理器(根据Jeffrey的书)被设计为同步不同处理器的缓存,因此我们可能看不到问题

顺便说一下,使用
lock
Interlocked
会从缓存中刷新变量,因此在读取属性时使用lock是安全的。发件人:

锁保证在锁内读取或修改的内存是一致的,锁保证一次只有一个线程访问给定的大块内存,依此类推

但是,在CLR规范中,当读取由另一个线程更新的值(不使用锁定同步构造)时,不能保证该值是最新的。事实上,在ARM上,我可以使用ThreadSafe类从以下位置获得旧值:

如果您的代码依赖于依赖于x86 CLR(而不是ECMA CLR规范)实现的无锁算法,则需要将volatile关键字适当地添加到相关变量中。一旦您将共享状态标记为volatile,CLR将为您处理所有事情。如果你和大多数开发者一样,你已经准备好在ARM上运行了,因为你已经使用锁来保护你的共享数据,正确地标记了易失性变量,并在ARM上测试了你的应用程序

因此,答案似乎是我可以在阅读时使用
,或者使我的字段
不稳定
,尽管也许我应该使用锁并尝试减少调用次数,作为一名从事编译器工作的人员:

锁太慢的情况非常少,并且由于不了解确切的内存模型而导致代码出错的可能性非常大。除了联锁操作的最琐碎的用法外,我不尝试编写任何低锁代码。我把“volatile”的用法留给真正的专家


你多久写一次关于价值观的文章,而不是读一次?这些方法对你有用吗?我想你需要看看这门课。在我看来,您有多个读卡器,但只有一个写卡器。我可以从线程池中的任何线程每秒最多写100次,即调用Update()。我将每秒从一个线程读取60次。@HansPassant:)我开始重新使用lock并减少调用次数,但我现在担心lock事件是否保证我读取相同的值?如果它被缓存在一个CPU中会使缓存失效吗?我指的是读取时的最新值。使用Interlocked将得到相同的结果-Interlocked执行添加操作后,读取,即myThreadSafe.Value仍然可以返回操作前的值!因为值可能位于CPU寄存器中(作为优化的结果),只有调用Update的线程才知道