【问题标题】:.NET Volatile.Read/Write and Interlocked scope.NET Volatile.Read/Write 和 Interlocked 范围
【发布时间】:2016-04-08 14:14:32
【问题描述】:

我已多次阅读threading manual 和相关的 MSDN 页面以及 SOquestions。尽管如此,我仍然不完全理解 Volatile.Read/Write 和联锁操作是否仅适用于相关变量,或者在该操作之前/之后的所有读/写。

例如,假设我有一个数组和一个计数器。

long counter = 0;
var values = new double[1000000];

values[42] = 3.1415;
// Is this line needed instead of simple assignment above,
// or the implicit full-fence of Interlocked will guarantee that 
// all threads will see the values[42] after interlocked increment?
//Volatile.Write(ref values[42], 3.1415);
Interlocked.Increment(ref counter);

互锁增量是否保证与我使用Volatile.Write(ref values[42], 3.1415); 而不是values[42] = 3.1415; 相同的结果。

如果我有一个引用类型数组怎么办,例如一些 POCO,并在互锁增量之前设置一个实例字段。隐式完整围栏是否适用于该线程之前的所有读/写,还是仅适用于计数器?

我正在实施scalable reader/writer scheme,我在 Joe Duffy 的帖子中找到了以下声明:

如果受保护的变量是对堆对象的引用,则每次触摸字段时都需要担心使用读保护。就像锁一样,这种技术不会组合。与简单锁定以外的任何其他方法一样,请谨慎使用此技术;尽管内置的获取和释放栅栏可以保护您免受内存模型重新排序问题的影响,但您可能会陷入一些简单的陷阱。

这只是一个不鼓励使用低锁结构的一般声明,还是以某种方式适用于上面的示例?

【问题讨论】:

  • 您只需要在此处获取/释放语义即可获得互锁增量提供的正确行为。保证是看到更新计数器的任何线程也将看到正在进行的写入。
  • 如果你不理解它,那么你不应该,永远使用它。它不会工作,保证。使用熊陷阱,您不会发现它不起作用,只有您的用户会。请改用 ReaderWriterLockSlim 类,它是由了解它并知道如何使其尽可能最佳的人编写的。
  • @HansPassant 世界上有这样一种东西叫做learning... SO 有助于解决这个问题:) 如果你阅读了关于读写锁实现的整个 Joe Duffy 文章系列,你会注意到数量级的性能差异。
  • 所以你想比 Joe Duffy 更好......好吧,你肯定会学到一些你还不知道的东西。
  • @HansPassant 我不想变得更好,我只是 prefer 200 Mops over 15 Mops 当没有争用...诸如“你太愚蠢了”之类的答案只是 not constructive。跨度>

标签: c# .net multithreading memory-barriers


【解决方案1】:

您可能缺少对栅栏的理解。这是阅读它们的最佳资源:http://www.albahari.com/threading/part4.aspx

简短的回答是Interlocked.Increment 发出一个完整的栅栏,它独立于它正在更新的变量。我相信Volatile.Write 发布了一个半栅栏。可以从Thread.MemoryBarrier 构建半栅栏。当我们说Interlocked.Increment 发出一个完整的栅栏时,这意味着Thread.MemoryBarrier 在操作之前和之后被调用。 Volatile.Write 在写入之前调用Thread.MemoryBarrier,之后调用Volatile.Read。栅栏决定了何时可以重新排序内存访问(它不是特定于变量的,因为Thread.MemoryBarrier 是无参数的)。

【讨论】:

  • 我在这里安全吗?
  • 最重要的是,互锁是原子的,没有额外的锁定。如果没有任何其他操作的锁定,您将无法复制它,因为值可能会在读写之间发生变化。是的,你很安全。
  • Thread.MemoryBarrier 放在对values[42] = 3.1415;Interlocked.Increment(ref counter); 的调用之间是多余的,因此添加 Volatile.Write 不会向您的代码添加任何内容。但是您可能应该阅读我提供的链接。如果values[42] 可以从不同的线程中读取,除非您使用有保证的原子操作来读取/写入它,否则您最终可能会遇到撕裂的读取。我不认为 CLR 对跨平台架构读取/写入双精度的原子性做出任何保证(例如,将双精度值视为 32 位平台上的两个 32 位值)。
  • @keith 我从那个链接开始我的问题,顺便说一句。我仅在计数器增加后才读取值。所以是的,我没有正确理解栅栏。
  • 抱歉,我复制了您的链接。如果您仅在计数器递增后读取值,您将获得相同的结果,并且只要在计数器递增之前没有任何内容访问值 [42],您应该会很好。如果您对完整解决方案的并发性有任何进一步的担忧,那么完整的代码帖子可能是一个好主意。请记住,除了小问题之外,几乎不可能正确地执行无锁代码。
猜你喜欢
  • 2014-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多