为啥 Interlocked.CompareExchange<T> 只支持引用类型?

Posted

技术标签:

【中文标题】为啥 Interlocked.CompareExchange<T> 只支持引用类型?【英文标题】:Why does Interlocked.CompareExchange<T> only support reference types?为什么 Interlocked.CompareExchange<T> 只支持引用类型? 【发布时间】:2010-02-11 01:06:40 【问题描述】:

免责声明:我的帖子显然总是很冗长。如果您碰巧知道标题问题的答案,请随意回答,而无需阅读下面我的扩展讨论。


System.Threading.Interlocked 类提供了一些非常有用的方法来帮助编写线程安全代码。更复杂的方法之一是CompareExchange,可用于计算可能从多个线程更新的运行总数。

由于CompareExchange的使用有点棘手,我认为为其提供一些辅助方法是一个相当常识的想法:

// code mangled so as not to require horizontal scrolling
// (on my monitor, anyway)
public static double Aggregate
(ref double value, Func<double, double> aggregator) 
    double initial, aggregated;

    do 
        initial = value;
        aggregated = aggregator(initial);
     while (
        initial != Interlocked.CompareExchange(ref value, aggregated, initial)
    );

    return aggregated;


public static double Increase(ref double value, double amount) 
    return Aggregate(ref value, delegate(double d)  return d + amount; );


public static double Decrease(ref double value, double amount) 
    return Aggregate(ref value, delegate(double d)  return d - amount; );

现在,也许我只是对普通的快乐感到内疚(我承认,这通常是真的);但是对我来说将上述方法提供的功能限制为仅double 值确实很愚蠢(或者,更准确地说,我必须为每种类型编写上述方法的重载版本我要支持)。为什么我不能这样做?

// the code mangling continues...
public static T Aggregate<T>
(ref T value, Func<T, T> aggregator) where T : IEquatable<T> 
    T initial, aggregated;

    do 
        initial = value;
        aggregated = aggregator(initial);
     while (
        !initial.Equals(
            Interlocked.CompareExchange<T>(ref value, aggregated, initial)
        )
    );

我不能这样做,因为Interlocked.CompareExchange&lt;T&gt; 显然有where T : class 约束,并且我不明白为什么。我的意思是,也许这是因为CompareExchange 已经有接受Int32Int64Double 等的重载;但这似乎不是一个好的理由。例如,在我的例子中,能够使用Aggregate&lt;T&gt; 方法来执行广泛的原子计算会非常方便。

【问题讨论】:

【参考方案1】:

Interlocked.CompareExchange 旨在使用处理器直接提供的本机原子指令来实现。在内部使用lock 是没有意义的(它是为无锁场景设计的)。

提供原子比较交换指令的处理器自然支持它作为小型的“寄存器大小”操作(例如,英特尔 x64 处理器上最大的比较交换指令是cmpxchg16b,它适用于 128 位值)。

任意值类型可能比这更大,并且可能无法通过单个指令进行比较交换。比较交换引用类型很容易。无论它在内存中的总大小如何,您都将比较和复制一个已知大小的小指针。对于Int32Double 等基本类型也是如此——它们都很小。

【讨论】:

我认为,当我在发布问题后不久在 MSDN 文档中读到此内容后,这对我来说很重要(对于接受 Object 参数的重载):“比较对象的引用相等性,而不是Object.Equals。因此,相同值类型(例如整数3)的两个装箱实例总是看起来不相等,并且不执行任何操作。不要将此重载与值类型一起使用。当然——它不会使用Equals 方法,而是自己检查引用。 很明显,我唯一的选择实际上就是自己编写所有这些重载方法(对于intlong 等)——如果我真的想走那条路.对吗? @Dan:如果不使用lock(交换一堆整数值),我不确定您如何原子地 为较大的值类型做到这一点。 compare-exchange 的要点是它是原子的。也就是说,不会有一个时间点你可以观察到一部分操作完成而另一部分没有完成。将一堆比较交换放在一起无助于使整个操作原子化(每个部分都是原子的,但整个操作不是原子的)。 @Dan:忽略我的最后评论。您的免责声明使我没有阅读您的问题,并且我从重载方法中误解了您的意思;)。是的,你必须这样做。 @Mehrdad:我想你失去了我。我的意图不是使用“一堆相互比较交换”......而是提供一种简单的方法来原子地执行任何类型的 single 聚合(例如,对平方值求和) .【参考方案2】:

因为重载专门用于比较和交换引用。它不使用 Equals() 方法执行相等检查。由于值类型永远不会与您要与之比较的值具有引用相等性,我猜他们将 T 限制为类以防止误用。

【讨论】:

我敢打赌,你的姓氏在你的同事中会给你很大的信任。 顺便说一句,这是我的 +1;我不是在冷嘲热讽。 LOL 没关系,我没有这样认为。顺便说一句,这个问题也很好。【参考方案3】:

我怀疑Interlocked.CompareExchange&lt;T&gt; 只是在后台执行原子指针交换。

尝试使用值类型执行此操作可能不会得到您期望的结果。

当然,您可以在使用 object 之前将值类型框起来。

【讨论】:

实际上将值类型装箱为object起作用。请参阅我对 Mehrdad 回答的第一条评论。

以上是关于为啥 Interlocked.CompareExchange<T> 只支持引用类型?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 DataGridView 上的 DoubleBuffered 属性默认为 false,为啥它受到保护?

为啥需要softmax函数?为啥不简单归一化?

为啥 g++ 需要 libstdc++.a?为啥不是默认值?

为啥或为啥不在 C++ 中使用 memset? [关闭]

为啥临时变量需要更改数组元素以及为啥需要在最后取消设置?

为啥 CAP 定理中的 RDBMS 分区不能容忍,为啥它可用?