如何在不锁定的情况下读取此实例原始线程安全?
下面的类的问题是当读取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的书),设计用于同步不同处理器的缓存,因此我们可能看不到问题。
顺便说一下,使用lock
和Interlocked
从缓存中刷新变量,因此在读取属性时使用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);
更多Interlocked
function可以在http://msdn.microsoft.com/en-us/library/system.threading.interlocked(v=vs.110).aspx找到。
使用基元时,这些函数非常棒。 对于更复杂的对象,将需要ReaderWriterLockSlim
等其他解决方案。