将 Interlocked.CompareExchange 与类一起使用
System.Threading.Interlocked.CompareExchange
运算符提供 Compare-And-Swap 操作的原子(因此线程安全)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);
比较和交换操作时,会失败。因此,我重写了类 X 的 Equals 和 == 运算符
public override bool Equals(object obj) { return y == ((X) obj).y; }
,现在我得到 Interlocked.Equals(a,b)
作为 true
,但是 CompareExchange
操作仍然失败。
有什么方法可以做到这一点吗?我想比较两个类实例,并根据比较结果为其中一个实例分配一个值。
System.Threading.Interlocked.CompareExchange
operator provides atomic (thus thread-safe) C# implementation of the Compare-And-Swap operation.
For example int i = 5; Interlocked.CompareExchange(ref i, 10, 5);
After this command, the int i would have a value = 10. And also the compare and exchange happens atomically (single operation).
When I tried using this with a class instance, the compare fails and the values are not exchanged.
public class X
{
public int y;
public X(int val) { y = val; }
}
Now when I do
X a = new X(1);
X b = new X(1);
X c = new X(2);
Interlocked.CompareExchange<X>(ref a, c, b);
The compare and Exchange operation fails. So, I overrided the Equals and the == operator for the class X as
public override bool Equals(object obj) { return y == ((X) obj).y; }
So, now I get Interlocked.Equals(a,b)
as true
, but the CompareExchange
operations still fails.
Is there any method to do this? I want to compare two class instances and assign one of them a value based on the comparision.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
Interlocked.CompareExchange
的正常使用是这样的模式:基本思想是,如果该字段在读入
oldValue
的时间和读取该字段的时间之间没有发生更改,CompareExchange
得到处理,然后newValue
将保存应存储到字段中的值。如果在计算过程中发生其他变化,则计算结果将被放弃,并且将使用新值重复计算。假设计算速度很快,最终的效果本质上是允许任意计算的行为就像它是原子的一样。如果您想使用
Equals()
相等性执行 Compare-Exchange 式操作,您可能应该执行以下操作:请注意,如果
someField
持有对某个对象的引用,该对象将比较等于compareValue
,并且在比较期间将其更改为保存对不同对象的引用,将根据compareValue
检查新值。将重复该过程,直到比较报告从字段字段读取的值不等于比较数,或者直到字段中的值对于Equals()
和CompareExchange
方法来完成。The normal use of
Interlocked.CompareExchange
is in the pattern:The basic idea is that if the field doesn't get changed between the time it's read into
oldValue
and the time theCompareExchange
gets processed, thennewValue
will hold the value that should be stored into the field. If something else changes it during the computation, the results of the computation will be abandoned and the computation will be repeated using the new value. Provided that the computation is fast, the net effect is essentially to allow an arbitrary computation to behave as though it's atomic.If you want to do a Compare-Exchange-style operation using
Equals()
equality, you should probably do something like:Note that if
someField
holds a reference to an object which would compare equal tocompareValue
, and during the comparison it is changed to hold a reference to a different object, that new value will be checked againstcompareValue
. The process will be repeated until either an comparison reports that the value read from the field field was not equal to the comparand, or until the value in the field remains unchanged long enough for both theEquals()
andCompareExchange
methods to complete.不,这是不可能的。
Interlocked.CompareExchange 基本上直接映射到能够自动比较和交换内存地址内容的汇编指令。我相信在 32 位模式下,可以使用 64 位版本的指令(以及 32 位和 16 位版本),而在 64 位模式下,我认为可以使用 128 位版本。但仅此而已。 CPU 没有“基于其特定
Equals
函数交换 .NET 类”指令。如果您想使用任意相等函数交换任意对象,您必须自己使用锁或其他同步机制来完成。
Interlocked.CompareExchange
重载 > 函数适用于对象引用,但由于上述原因它使用引用相等性。它只是比较引用,然后交换它们。根据您的评论,使用结构无法解决问题。同样,CPU 只能原子地比较和交换某些固定大小的值,并且它没有抽象数据类型的概念。可以使用引用类型,因为引用本身具有有效的大小,并且可以由 CPU 与另一个引用进行比较。但 CPU 对引用指向的对象一无所知。
No. It can't be done.
Interlocked.CompareExchange
basically maps directly to an assembly instruction which is able to atomically compare and swap the contents of a memory address. I believe in 32-bit mode, a 64-bit version of the instruction is available (as well as 32- and 16-bit versions), and in 64-bit mode, I think a 128-bit version is available. But that's all. The CPU doesnt' have a "swap .NET class based on its specificEquals
function" instruction.If you want to swap arbitrary objects, using arbitrary equality functions, you have to do it yourself, using locks or other synchronization mechanisms.
There is an overload of the
Interlocked.CompareExchange
function which works on object references, but it uses reference equality for the above reason. It simply compares the references, and then swaps them.In response to your comment, using structs would not solve the problem. Again, the CPU can only atomically compare and swap values of certain fixed sizes, and it has no notion of abstract datatypes. Reference types can be used because the reference itself has a valid size, and can be compared against another reference by the CPU. But the CPU knows nothing about the object that the reference points to.
我觉得整个页面存在一些弥漫性的混乱。首先,评论员认为该问题包含一个危险的假设是正确的:
否,仅当
i
的值在此期间未更改为5
以外的值时才有效。尽管这在此处显示的代码中似乎不太可能,但使用CompareExchange
的全部意义在于它应该是可能的,因此这是这里的关键技术问题。我担心OP可能不理解Interlocked.CompareExchange的目的,特别是因为他没有检查返回值(见下文)。现在原始问题的文本是:
由于“this”这个词没有可行的先行词,我们也许应该将后面的句子视为问题,释义为:
“有没有办法比较两个类实例并为其中一个实例分配一个值基于比较?”
不幸的是,这个问题仍然不清楚,或者可能与原子操作关系不大。首先,你不能“为[类实例]分配一个值”。这根本没有意义。对类实例的引用是一个值,但无法将任何内容“分配”给类实例本身。这是与值类型的一个主要区别,值类型可以相互分配。您可以使用
new
运算符创建一个实例,但您仍然只能获得对其的引用。同样,这些可能看起来像是技术细节,但如果问题确实涉及无锁并发,那么它们就是关键点。接下来,
Interlocked.CompareExchange
函数不会根据值来确定存储位置,而是有条件地将值存储到(给定的)位置 strong>,意味着它要么存储值(成功),要么保持存储位置不变(失败),同时可靠地指示发生了哪一种情况。这意味着短语“基于比较”对于替代行动应该是什么是不完整的。看看OP问题的前一部分,一个最好的猜测可能是问题正在寻求有条件地操纵实例引用,而原子性是一个转移注意力的东西。这很难知道,因为如上所述,CompareExchange(用于陈述问题)不会在内存中“交换”两个值,它只可能“存储”一个值。
通过
Equals
重载,这可以被简化:OP 关注内部字段
y
的相等性似乎增加了对问题的这种解释走上正确轨道的可能性。但显然,这些答案与 Interlocked.CompareExchange 无关。我们需要更多信息来了解为什么 OP 认为赋值必须是原子的。因此,我们应该注意到,也可以原子地交换现有实例中的 y 值:
或者交换实例引用,现在应该很明显了引用相等仅根据“引用相等”来定义:
从这里开始,问题需要更加清晰。例如,要重述本页其他地方的评论,但更强烈的是,不检查 Interlocked.CompareExchange 的返回值是一个错误。
这就是我在上面的示例中存储返回值的原因,以及我认为它的名称合适的原因。不对返回值进行分支就是不理解无锁(“乐观”)并发的基本原理,对此的讨论超出了本问题的范围。有关精彩的介绍,请参阅 Joe Duffy 撰写的 Windows 上的并发编程。
最后,我认为 OP 不太可能真正需要基于任意考虑以原子方式存储类引用,因为这是一个极其专业的操作,通常只在全面的无锁系统设计的关键时刻才需要。但是(与另一个答案相反)按照@supercat所描述的方式当然是可能的。
因此,请不要认为您无法在 .NET 中编写无锁代码,或者类引用对于
Interlocked
操作来说是任何类型的问题;事实上,情况恰恰相反:如果您实际上需要执行一个原子操作,在两个不同的存储位置之间进行选择或以其他方式影响多个内存位置,那么使用一种设计很简单,其中纠缠的位置被包装在一个简单的包含class 然后为您提供一个可以以无锁方式原子交换的引用。无锁编码在 .NET 中轻而易举,因为在乐观路径失败的罕见情况下,内存管理重试对象的麻烦更少。可以这么说,根据我的经验,无锁并发的任何重要方面都是我无法在 C#/.NET/CLR 中实现的,即使有时有点粗糙围绕边缘,正如您可以从 https://stackoverflow.com/a/5589515/147511。
I feel there is some some diffuse confusion throughout this page. First, a commentator is correct that the question contains an dangerous assumption:
No, only if the value of
i
hasn't changed to a value other than5
in the meantime. Although that seems improbable in the code shown here, the whole point of usingCompareExchange
is that it should be possible, so it's a critical technicality here. I worry that the OP may not understand the purpose ofInterlocked.CompareExchange
, particularly because he is not examining the return value (see below).Now the text of the original question was:
Since there's no viable antecedent for the word "this", we should perhaps consider as the question here the sentence which comes after, giving the paraphrase:
"Is there any way to compare two class instances and assign one of them a value based on the comparison?"
Unfortunately, this question is still unclear, or possibly has little to do with atomic operations. Firstly, you can't "assign [a class instance] a value." It just doesn't make sense. A reference to a class instance is a value, but there's no way to "assign" anything to a class instance itself. That's a major difference versus value types, which can be assigned to each other. You can create an instance using the
new
operator, but you'll still just get a reference to it. Again, these may seem like technicalities, but are critical points if the question is truly meant to concern lock-free concurrency.Next, the
Interlocked.CompareExchange
function doesn't condition a storage location upon a value, but rather it conditionally stores a value to a (given) location, meaning that it either stores the value (success), or leaves the storage location unchanged (failure), while reliably indicating which of these occurred.This means that the phrase "based on the comparison" is incomplete about exactly what the alternative actions are supposed to be. Looking at the earlier part of the OP's question, one best guess might be that the question is looking to conditionally manipulate the instance references, and atomicity is a red herring. It's hard to know because, as noted above,
CompareExchange
(which was used to state the question) doesn't "swap" two values in memory, it only possibly "stores" one value.With the
Equals
overload, this could be streamlined:The OP's focus on equality of the internal field
y
seems to increase the likelihood that this interpretation of the question is on the right track. But obviously, answers along these lines have nothing to do withInterlocked.CompareExchange
. We would need more information to know why the OP thinks the assignment has to be atomic.So alternatively then, we should note that it is also possible to atomically swap the
y
values in the existing instances:Or swap instance references, and by now it now should be obvious that equating references is only defined in terms of "reference equality":
To proceed from here, the question would need more clarity. For example, to restate a comment made elsewhere on this page, but more strongly, it is an error to not examine the return value of Interlocked.CompareExchange.
This is why I stored the return value in the example above, and how I deemed its name appropriate. To not branch on the return value is to not understand the basic principles of lock-free ("optimistic") concurrency, a discussion of which is beyond the scope of this question. For an excellent introduction, see Concurrent Programming on Windows by Joe Duffy.
Finally, I think it's quite unlikely that the OP really needs to atomically store a class references based on arbitrary considerations, because this is an extremely specialized operation that's typically only necessary at the very crux of a comprehensive lock-free system design. But (contrary to another answer) it's certainly possible along the lines of what @supercat describes.
So please don't get the impression that you can't write lock-free code in .NET, or that class references are any kind of problem for the
Interlocked
operations; in fact it's actually quite the opposite: if you actually need to do an atomic operation which selects between two different storage locations or otherwise affects multiple memory locations, it's simple to use a design where the entangled locations are wrapped in a trivial containing class which then gives you a single reference that can be atomically swapped in lock-free fashion. Lock-free coding is a breeze in .NET, since its less hassle to memory-manage retry objects for the rare cases where the optimistic path fails.Suffice it to say that, in my experience, there is no essential aspect of lock-free concurrency that I have not been able to achieve in C#/.NET/CLR, even if it's sometimes a bit rough around the edges, as you might ascertain from https://stackoverflow.com/a/5589515/147511.