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

时间:2013-12-01 04:20:23

标签: 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 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确实说:“这确保了字段中始终存在最新值”,但是我读到了{{ 3}}写入然后读取CPU指令在使用volatile时仍然可以交换,在这种情况下,我可以获得myThreadSafe.Value的先前值,也许我可以忍受 - 只有一次更新。

最有效的方法是什么,我总能获得myThreadSafe.Value的最新价值?

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

  • 86
  • AMD64(虽然我可以构建为x86)
  • 的PowerPC
  • ARM(仅限Little-endian)

使用运行时:

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

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

2 个答案:

答案 0 :(得分:2)

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

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

  

由于两个处理器可以具有不同的缓存,这些缓存是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,但也许我应该使用锁定并尝试减少调用次数,作为一个工作的人编译器says

  

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

答案 1 :(得分:1)

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

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

简单地:

Interlocked.Add(ref value, 47);

http://msdn.microsoft.com/en-us/library/system.threading.interlocked(v=vs.110).aspx

可以找到更多Interlocked个功能

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