c# - 如何使读取此实例原始线程安全而无需锁定?

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

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

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

  • x86
  • AMD64(尽管我可以构建为 x86)
  • PowerPC
  • ARM(仅限小端)

使用运行时:

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

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

最佳答案

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

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

Since two processors can have different caches, which are copies of the ram, they can have different values. In x86 and x64 processors (according to Jeffrey’s book) are designed to sync the caches of different processors so we may not see the problem.

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

Locks guarantee that memory read or modified inside the lock is observed to be consistent, locks guarantee that only one thread accesses a given hunk of memory at a time, and so on.

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

If your code relies on lock-free algorithms that depend on the implementation of the x86 CLR (rather than the ECMA CLR specification), you’ll want to add the volatile keyword to relevant variables as appropriate. Once you’ve marked shared state as volatile, the CLR will take care of everything for you. If you’re like most developers, you’re ready to run on ARM because you’ve already used locks to protect your shared data, properly marked volatile variables and tested your app on ARM.

所以看来答案是我可以在读取时使用或使我的字段 volatile ,尽管也许我应该使用锁并尝试减少调用次数,作为一个从事编译器工作的人says :

The number of situations in which a lock is too slow is very small, and the probability that you are going to get the code wrong because you don't understand the exact memory model is very large. I don't attempt to write any low-lock code except for the most trivial usages of Interlocked operations. I leave the usage of "volatile" to real experts.

关于c# - 如何使读取此实例原始线程安全而无需锁定?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20308692/

相关文章:

c#.NET USB 设备持久标识符

c# - 线程安全的StreamWriter C#怎么做呢? 1个

java - 同步语句——创建对象来提供锁,以便 c1 的更新不会与 c2 的更新交错

c# - 未知类型的泛型

c# - 具有可选参数和查询参数的 Web Api 属性路由

c# - 如何使用 linq 从表中搜索任何属性?

c# - 如何在 Mac (Mono) 上使用从 nuget 下载的包

.net - C# 如果我以优先级 = RealTime 运行进程,GC 会发生什么?

c# - 使用 XmlSerializer 反序列化 List<int> 导致额外的项目

java - 我应该在我的 java 视频应用程序中实现消费者/生产者模式吗?如果是,如何实现?