c# - 这个 MSDN CompareExchange 示例如何不需要 volatile 读取?

标签 c# .net multithreading volatile interlocked

我一直在寻找一个使用支持按任意值递增的 Interlocked 的线程安全计数器实现,并直接从 Interlocked.CompareExchange 中找到了这个示例文档(为简单起见略有更改):

private int totalValue = 0;

public int AddToTotal(int addend)
{
    int initialValue, computedValue;
    do
    {
        // How can we get away with not using a volatile read of totalValue here?
        // Shouldn't we use CompareExchange(ref TotalValue, 0, 0)
        // or Thread.VolatileRead
        // or declare totalValue to be volatile?           
        initialValue = totalValue;

        computedValue = initialValue + addend;

    } while (initialValue != Interlocked.CompareExchange(
        ref totalValue, computedValue, initialValue));

    return computedValue;
}

 public int Total
 {
    // This looks *really* dodgy too, but isn't 
    // the target of my question.
    get { return totalValue; }
 }

我明白这段代码试图做什么,但我不确定在分配给添加到的临时变量时如何不使用共享变量的 volatile 读取而逃脱。

initialValue 是否有可能在整个循环中保持一个过时的值,从而使函数永远不会返回?或者 CompareExchange 中的内存屏障(?)是否消除了任何这种可能性?任何见解将不胜感激。

编辑:我应该澄清一下,如果 CompareExchange 导致 后续 读取 totalValue 是最新的 last CompareExchange 调用,那么这段代码就可以了。但这有保证吗?

最佳答案

如果我们读取一个陈旧的值,那么 CompareExchange 将不会执行交换——我们基本上是在说,“只有当该值确实是我们基于我们的值时才执行操作计算上。”只要在某个点我们得到正确的值,就可以了。如果我们一直读取相同的陈旧值将是一个问题,所以 CompareExchange 从未 通过检查,但我强烈怀疑 CompareExchange内存屏障意味着至少在循环结束后,我们将读取一个最新值。可能发生的最坏情况是永远循环 - 重要的一点是我们不可能以不正确的方式更新变量。

(是的,我认为您是对的,Total 属性是不可靠的。)

编辑:换句话说:

CompareExchange(ref totalValue, computedValue, initialValue)

意思是:“如果当前状态真的是initialValue,那么我的计算是有效的,你应该将它设置为computedValue。”

至少有两个原因可能导致当前状态错误:

  • initialValue = totalValue; 赋值使用了具有不同旧值的陈旧读取
  • totalValue那次分配后发生了一些变化

我们根本不需要以不同的方式处理这些情况 - 所以只要在某个点我们开始看到最新值,就可以进行“廉价”读取...而且我相信 CompareExchange 中涉及的内存屏障将确保当我们循环时,我们看到的陈旧值只会与之前的 CompareExchange 调用一样陈旧。

编辑:澄清一下,我认为样本是正确的当且仅当 CompareExchange 构成关于totalValue 的内存屏障。如果不是这样——如果我们在继续循环时仍然可以读取 totalValue 的任意旧值——那么代码确实被破坏了,并且可能永远不会终止。

关于c# - 这个 MSDN CompareExchange 示例如何不需要 volatile 读取?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7556727/

相关文章:

java - 如何使用 Java 将线程委托(delegate)给多个服务器并跟踪哪个线程位于哪个服务器上

c++ - Boost::thread 的可中断线程实现

multithreading - Cython Prange失败并显示致命Python错误: PyThreadState_Get: no current thread

c# - 如何使用正确的可见性修饰符来实现这个类结构?

c# - ViewBag 不保存状态

c# - 带有 xmlSerializer 的 UWP - System.InvalidOperationException : Unable to generate a temporary class (result=1) compile error

c# - 与异步/等待 C# 作斗争

c# - 记录 ninject 解决的依赖项应用程序启动

c# - "System.IO.IOException: The process cannot access the file ' C :\Test\test. 文本 ' because it is being used by another process"

c# - 如何在C#中截取滚动窗口的屏幕截图