繁体   English   中英

如何使该实例原始线程安全地读取而不锁定?

[英]How to make reading this instance primitive thread-safe without locking?

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

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

更新:此代码将被编译并在CPU体系结构上运行:

  • 86
  • AMD64(尽管我可以将其构建为x86)
  • PowerPC的
  • ARM(仅小端)

使用运行时:

  • CLR v4.0
  • Mono(我不确定Mono运行时版本,但是否与Mono版本相对应:至少为3.0)。

我希望所有构建都使用相同的代码!

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

该代码恰好在x86和AMD64上是线程安全的,因为它们在写入变量时使CPU缓存无效,从而导致后续的读取从内存中读取变量。 引用Shafqay Ahmed 引用 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/zh-cn/magazine/jj553518.aspx中获得旧值:

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

因此似乎答案是我可以在阅读时使用lock或使字段volatile ,尽管我可能应该使用锁定并尝试减少调用次数,正如从事编译器工作的人说的

锁太慢的情况的数量非常少,并且由于您不了解确切的内存模型而导致代码出错的可能性非常大。 除了互锁操作的最简单用法之外,我不会尝试编写任何低锁代码。 我将“易失性”的用法留给真正的专家使用。

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

要处理我提到的奇怪问题,您可以像使用锁一样使用锁。 但是您似乎想要一个不同的解决方案。 如果您不想锁定读取,但是要确保写入是原子的,以便在多线程写入期间进行读取时读取不会返回奇数或其他杂乱的事物,那么我建议使用Interlocked类。

只是:

Interlocked.Add(ref value, 47);

可以在http://msdn.microsoft.com/zh-cn/library/system.threading.interlocked(v=vs.110).aspx中找到更多Interlocked功能

当使用基元时,这些功能非常有用。 对于更复杂的对象,将需要其他解决方案,例如ReaderWriterLockSlim和其他解决方案。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM