26

我正在使用这样的配置:

  • .NET 框架 4.5
  • 视窗服务器 2008 R2
  • HP DL360p Gen8(2 * Xeon E5-2640,x64)

我的程序中某处有这样的字段:

protected int HedgeVolume;

我从几个线程访问这个字段。我假设因为我有多处理器系统,所以这个线程可能在不同的处理器上执行。

我应该怎么做才能保证任何时候我使用这个字段最新的值是“读取”?并确保当我“写入”值时它立即可供所有其他线程使用?

我该怎么办?

  • 保持原样。
  • 声明它volatile
  • 使用Interlocked类访问字段
  • 使用 .NET 4.5访问字段的Volatile.Read方法Volatile.Write
  • 采用lock

我只需要最简单的方法来使我的程序在此配置上运行我不需要我的程序在另一台计算机或服务器或操作系统上运行。我还想要最小的延迟,所以我正在寻找最快的解决方案,它总是可以在这个标准配置(多处理器 intel x64,.net 4.5)上工作。

4

8 回答 8

8

您的问题缺少一个关键要素……该领域数据的完整性有多重要?

volatile为您提供性能,但如果一个线程当前正在向该字段写入更改,则在完成之前您不会获得该数据,因此您可能会访问过时的信息,并可能覆盖另一个线程当前正在执行的更改。如果数据很敏感,您可能会遇到难以跟踪的错误。但是,如果您正在进行非常快速的更新,请在不读取值的情况下覆盖该值,并且不在乎偶尔会过时(几毫秒)数据,那就去做吧。

lock保证一次只有一个线程可以访问该字段。您可以仅将其放在写入字段的方法上,而不要理会读取方法。不利的一面是,它很慢,并且可能会在另一个线程执行其任务时阻塞一个线程。但是,您确信您的数据保持有效。

Interlock存在以保护自己免受调度程序上下文切换的影响。我的意见?除非您确切地知道为什么要使用它以及确切地使用它,否则不要使用它。它提供了选择,但有很多选择会带来很大的问题。它可以防止在更新变量时进行上下文切换。它可能不会像您认为的那样做,也不会阻止并行线程同时执行它们的任务。

于 2012-10-30T07:34:37.000 回答
5

你想用Volatile.Read().

当您在 x86 上运行时,C# 中的所有写入都相当于Volatile.Write(),您只需将其用于 Itanium。

Volatile.Read()将确保您获得最新的副本,而不管上次是哪个线程编写的。

这里有一篇很棒的文章,C# Memory Model Explained

它的摘要包括,

在某些处理器上,编译器不仅必须避免对易失性读写进行某些优化,还必须使用特殊指令。在多核机器上,不同的核有不同的缓存。默认情况下,处理器可能不会费心使这些缓存保持一致,并且可能需要特殊指令来刷新和刷新缓存。

希望这很明显,除了需要volatile阻止编译器对其进行优化之外,还有处理器。

但是,在 C# 中,所有写入都是易失性的(与 Java 中的说法不同),无论您写入的是易失性字段还是非易失性字段。因此,上述情况实际上从未在 C# 中发生过。易失性写入会更新线程的缓存,然后将整个缓存刷新到主内存。

你不需要Volatile.Write()。更权威的来源在这里,Joe Duffy CLR 内存模型。但是,您可能需要它来停止编译器对其重新排序。

由于所有 C# 写入都是易失的,因此您可以将所有写入视为直接进入主内存。常规的非易失性读取可以从线程的缓存中读取值,而不是从 main

你需要Volatile.Read()

于 2012-11-01T10:59:46.017 回答
2

当你开始设计一个并发程序时,你应该按照优先顺序考虑这些选项:

1) 隔离性:每个线程都有自己的私有数据
2) 不变性:线程可以看到共享状态,但它永远不会改变
3) 可变共享状态:用锁保护对共享状态的所有访问

如果您达到 (3),那么您实际上需要多快?

获取一个无竞争的锁大约需要 10ns(10 -8秒)——这对于大多数应用程序来说已经足够快了,并且是保证正确性的最简单方法。

使用您提到的任何其他选项都会带您进入低锁编程领域,这非常难以正确。


如果你想学习如何编写并发软件,你应该阅读这些:

简介:Joe Albahari 的免费电子书- 大约需要一天时间阅读

圣经:Joe Duffy 的“Windows 上的并发编程” ——大约需要一个月的时间阅读

于 2012-10-30T07:50:49.940 回答
0

取决于你做什么。仅供阅读,易失性最简单,互锁允许更多控制。锁定是不必要的,因为它比您描述的问题更简洁。不确定 Volatile.Read/Write,从未使用过它们。

于 2012-10-24T13:59:09.130 回答
0

volatile- 不好,有一些问题(参见 Joe Duffy 的博客)

如果您所做的只是读取值或无条件写入值 - 使用Volatile.ReadVolatile.Write

如果您需要读取并随后写入更新的值 - 使用lock语法。但是,您可以使用类功能在不使用锁定的情况下实现相同的效果Interlocked,但这更复杂(涉及CompareExchanges 以确保您正在更新读取的值,即自读取操作以来没有被修改+如果值被修改后重试的逻辑读)。

于 2012-10-24T14:08:31.577 回答
0

由此我可以理解,您希望能够读取它在字段中写入的最后一个值。让我们类比一下数据的sql并发问题。如果您希望能够读取字段的最后一个值,则必须进行原子指令。如果有人正在写入一个字段,则所有线程必须被锁定以进行读取,直到该线程完成写入事务。之后,对该线程的每次读取都是安全的。问题不在于阅读,而在于写作。如果你问我,只要它的书面就足够了,就锁定那个领域......

于 2012-11-05T08:49:52.933 回答
0

首先看看这里:易失性 vs. 互锁 vs. 锁定

volatile 修饰符对于多内核 CPU 来说是一个不错的选择。

但这足够了吗?这取决于您如何计算新的 HedgeVolume 值!

  • 如果您的新 HedgeVolume 不依赖于当前 HedgeVolume,那么您就完成了 volatile。

  • 但是如果 HedgeVolume[x] = f(HedgeVolume[x-1]) 那么你需要一些线程同步来保证 HedgeVolume 在你计算和分配新值时不会改变。在这种情况下,lock 和 Interlocked szenarios 都适用。

于 2012-11-05T20:12:01.200 回答
0

我有一个类似的问题,发现这篇文章非常有帮助。读起来很长,但我学到了很多!

于 2012-11-06T04:25:16.410 回答