将Interlocked.CompareExchange与类一起使用

时间:2011-07-14 08:23:06

标签: c# .net multithreading interlocked

System.Threading.Interlocked.CompareExchange运算符提供了比较和交换操作的原子(因此是线程安全的)C#实现。

例如int i = 5; Interlocked.CompareExchange(ref i, 10, 5);在此命令之后,int i将具有值= 10.并且比较和交换以原子方式发生(单个操作)。

当我尝试将它与类实例一起使用时,比较失败并且不交换值。

   public class X
   {
       public int y;
       public X(int val) { y = val; }
   }

现在我做的时候

    X a = new X(1);
    X b = new X(1);
    X c = new X(2);
    Interlocked.CompareExchange<X>(ref a, c, b);

比较和Exchange操作失败。所以,我把等级X的等于和==运算符覆盖为

    public override bool Equals(object obj) { return y == ((X) obj).y; }

所以,现在我将Interlocked.Equals(a,b)作为true,但CompareExchange操作仍然失败。

有没有办法做到这一点?我想比较两个类实例,并根据比较为其中一个赋值。

3 个答案:

答案 0 :(得分:20)

没有。它无法完成。

Interlocked.CompareExchange基本上直接映射到汇编指令,该汇编指令能够原子地比较和交换存储器地址的内容。我相信在32位模式下,可以使用64位版本的指令(以及32位和16位版本),在64位模式下,我认为可以使用128位版本。但就是这样。 CPU根据其特定的Equals函数“指令没有'交换.NET类。

如果要使用任意相等函数交换任意对象,则必须使用锁或其他同步机制自行完成。

Interlocked.CompareExchange函数的an overload适用于对象引用,但由于上述原因,它使用引用相等。它只是比较引用,然后交换它们。

在回复您的评论时,使用结构不能解决问题。同样,CPU只能原子地比较和交换某些固定大小的值,并且它没有抽象数据类型的概念。可以使用引用类型,因为引用本身具有有效大小,并且可以与CPU的另一个引用进行比较。但CPU对引用指向的对象一无所知。

答案 1 :(得分:3)

Interlocked.CompareExchange的正常使用方式为:

SomeType oldValue;
do
{
  oldValue = someField;
  someType newValue = [Computation based on oldValue]
} while (CompareExchange(ref someField, newValue, oldValue) != oldValue);

基本思想是,如果字段在读入oldValue的时间和处理CompareExchange的时间之间没有变化,那么newValue将保留该值应存入该领域。如果在计算过程中其他内容发生了变化,则将放弃计算结果,并使用新值重复计算。如果计算速度很快,那么净效应本质上就是允许任意计算表现得像原子一样。

如果您想使用Equals()等式进行Compare-Exchange风格的操作,您应该执行以下操作:

SomeType newValue = desired new value;
SomeType compareValue = desired comparand;
SomeType oldValue;
do
{
  oldValue = someField;
  if (!oldValue.Equals(compareValue) return oldValue;
} while (CompareExchange(ref someField, newValue, oldValue) != oldValue);
return oldValue;

请注意,如果someField保存对比较等于compareValue的对象的引用,并且在比较期间将其更改为保存对其他对象的引用,则将检查该新值反对compareValue。将重复该过程,直到比较报告从字段字段读取的值不等于比较,或者直到字段中的值保持不变足够长时间Equals()CompareExchange方法来完成。

答案 2 :(得分:1)

我觉得整个页面都有一些混乱。首先,评论员是正确的,该问题包含一个危险的假设:

int i = 5; 
Interlocked.CompareExchange(ref i, 10, 5);
     

在此命令之后, int i的值为= 10

不,仅当i的值在此期间没有更改为5以外的值时。虽然在这里显示的代码中似乎不太可能,但使用CompareExchange的全部意义在于它应该是可能的,所以它在这里是一个关键的技术性。我担心OP可能不理解Interlocked.CompareExchange的目的,特别是因为他没有检查返回值(见下文)。

现在原始问题的文字是:

  

&#34;有没有办法做到这一点?我想比较两个类实例,并根据比较为其中一个指定一个值。&#34;

由于“&#34;”这个词没有可行的先行词,我们或许应该把这句话的问题考虑在后面,给出这样的解释:

&#34;有没有办法比较两个类实例并根据比较为其中一个指定一个值?&#34;

不幸的是,这个问题仍然不清楚,或者可能与原子操作无关。首先,您不能为[类实例]分配一个值。&#34;它没有意义。对类实例引用一个值,但是没有办法&#34;分配&#34;任何类实例本身。与值类型相比,这是一个重大差异,可以相互分配。您可以使用new运算符创建实例,但您仍然只是获得对它的引用。同样,这些看起来似乎是技术性问题,但如果问题确实涉及无锁并发,则这些问题是关键点。

接下来,Interlocked.CompareExchange函数不会在值上调整存储位置,而是有条件地将值存储到(给定)位置,意味着它要么存储值(成功),要么保持存储位置不变(失败),同时可靠地指示出现了哪些。

这意味着短语&#34;基于比较&#34;关于确切的替代行动应该是什么是不完整的。看看OP问题的早期部分,最好的猜测可能是问题是有条件地操纵实例引用,而原子性是一个红色的鲱鱼。很难知道,因为如上所述,CompareExchange(曾用于陈述问题)不会交换&#34;内存中的两个值,它可能只是&#34;存储&#34;一个价值。

X a = new X(1);
X b = new X(1);
X c = new X(2);

if (a.y == b.y)
    a = c;
else
    // ???

Equals超载,可以简化:

if (a == b)
    a = c;
else
    // ???
OP关注内部领域y的平等似乎增加了对问题的解释在正确轨道上的可能性。但显然,这些方面的答案与Interlocked.CompareExchange无关。我们需要更多的信息才能知道为什么OP认为赋值是原子的。

因此,我们可以注意到,也可以在现有实例中以原子方式交换y值:

var Hmmmm = Interlocked.CompareExchange(ref a.y, c.y, b.y);

或者交换实例引用,现在显而易见的是,等同引用只能用&#34;引用相等来定义&#34;:

var Hmmmm = Interlocked.CompareExchange(ref a, c, b);

从这里开始,问题需要更加明确。例如,要重新声明在此页面上其他位置发表的评论,但更强烈的是, 不检查Interlocked.CompareExchange的返回值 是错误的。

这就是为什么我在上面的例子中存储了返回值,以及我认为它的名称是否合适。不分支返回值是不理解无锁(&#34;乐观&#34;)并发的基本原则,其讨论超出了本问题的范围。有关精彩的介绍,请参阅Joe Duffy撰写的Concurrent Programming on Windows

最后,我认为OP不太可能真的需要基于任意考虑来原子地存储类引用,因为这是一种非常专业的操作,通常只需要在非常关键的全面的无锁系统设计。但是(与另一个答案相反)它确实可能与@supercat所描述的一致。

因此,请不要觉得您无法在.NET中编写无锁代码,或者类引用是Interlocked操作的任何问题;实际上它实际上恰恰相反:如果你真的需要做一个原子操作,在两个不同的存储位置之间进行选择或者影响多个存储位置,那么使用的设计很简单纠缠的位置包含在一个简单的包含类中,然后为您提供一个可以无锁方式原子交换的单个引用。无锁编码在.NET中是轻而易举的,因为在乐观路径失败的罕见情况下,内存管理重试对象的麻烦较少。

我只想说,根据我的经验,我无法在 C#/。NET / CLR 中实现无锁并发的基本方面,即使它&# 39; s有时在边缘有点粗糙,你可以从https://stackoverflow.com/a/5589515/147511确定。