我一直在寻找一个使用支持按任意值递增的 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/