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

下面的类的问题是当读取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是答案, MSDN确实说:“这确保了字段中始终存在最新值”,但是我在其他地方读取写入然后读取CPU指令仍然可以在使用volatile时进行交换在这种情况下,我可以获得myThreadSafe.Value的先前值,也许我可以忍受 – 只有一次更新。

什么是最有效的方法,我将始终获得myThreadSafe.Value的最新值?

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

  • 86
  • AMD64(虽然我可以构建为x86)
  • PowerPC的
  • ARM(仅限小端)

使用运行时:

  • CLR v4.0
  • 单声道(我不确定单声道运行时版本,但如果它们对应于Mono版本:至少3.0)。

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

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

代码恰好在x86和AMD64上是线程安全的,因为它们在写入变量时使CPU缓存无效,从而导致后续读取从内存中读取变量。 引用Shafqay Ahmed的话引述杰弗里里希特:

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

顺便说一下,使用lockInterlocked从缓存中刷新变量,因此在读取属性时使用lock是安全的。 来自http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three.aspx :

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

但是,当读取由另一个线程更新的值(不使用锁定同步构造)时,CLR规范中不能保证最新。 确实在ARM上我可以使用ThreadSafe类来获取旧值,来自http://msdn.microsoft.com/en-us/magazine/jj553518.aspx :

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

因此,似乎答案是我可以在读取时使用lock或使我的字段volatile ,但也许我应该使用锁定并尝试减少调用次数,因为在编译器上工作的人说 :

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

我不确定你的“最新价值”是什么意思。 您可以使用锁来确保在写入时不会读取Value ,这可能会产生一些奇怪的现象,但如果您阅读它然后写入它,您将没有最新的值。

为了处理我所提到的奇怪之处,你可以像你一样使用锁。 但你似乎想要一个不同的解决方案。 如果您不想锁定读取,但是您希望确保写入是primefaces的,以便在multithreading写入期间执行读取时读取不会返回奇数或其他一些混乱的东西,那么我会建议使用Interlocked类。

只是:

 Interlocked.Add(ref value, 47); 

更多Interlockedfunction可以在http://msdn.microsoft.com/en-us/library/system.threading.interlocked(v=vs.110).aspx找到。

使用基元时,这些函数非常棒。 对于更复杂的对象,将需要ReaderWriterLockSlim等其他解决方案。