Interlocked.Exchange影响以下指令

问题描述 投票:0回答:1

如果线程 1 运行:

this.Field.Flag = false;

...

var oldValue = Interlocked.Exchange(ref this.Field, newValue);
oldValue.Flag = true;

和线程 2 看到

oldValue.Flag == true
,是否保证它也看到
this.Field == newValue
即使它不使用 Interlocked/Volatile 读取
this.Field

即是否保证 Interlocked.Exchange 之后的指令效果仅在 Interlocked.Exchange 的效果本身可见之后才可见?

c# locking memory-barriers lock-free interlocked
1个回答
0
投票

是的,Interlocked.Exchange 是与交换相关的完整屏障(如 x86

lock xchg [mem]
),所以是的,它保证对面的商店顺序变得全局可见(即提交到一致的 L1d 缓存),并保留 StoreStore 顺序。 (以及强制执行 StoreLoad、LoadLoad 和 LoadStore 排序。)

但是读者必须确保它的负载是按顺序订购的。彼此,如果没有

Volatile.Read
或更强大的,就不会发生这种情况。否则
this.Field
的加载可能会在缓存中命中并在任何一个存储变得可见之前读取一个值,而较早的
oldValue.Flag
加载可能会在缓存中丢失并且只能获取较晚的值,这只是一个可能机制的示例 用于 LoadLoad 在运行时重新排序 在像 AArch64 这样的弱排序 ISA 上

编译时重新排序在 reader 中也是可能的,这是在 x86-64 上出错的唯一方法,其中硬件内存模型只允许 StoreLoad 重新排序。 (程序顺序 + 带存储转发的存储缓冲区。)

在其他更糟糕的情况下,您有编写者方面的发布语义(

fence
oldValue.Flag=...
至少与
Volatile.Write
上的
oldValue.Flag
一样强大),但如果您不使用,您将得不到任何保证一个获取负载,如果它看到该值,它将与它同步。 https://preshing.com/20120913/acquire-and-release-semantics/


顺便说一句,你的例子对我来说似乎很奇怪。您在由其中一个线程运行的函数中有

var oldValue = Interlocked.Exchange(...)
。所以它是一个局部变量。另一个线程怎么会看到它呢?它是对其他线程已经可以看到的东西的引用吗?并且不会在赋值之前为
oldValue
本身写一个值可能会使
oldValue.Flag == true
?但是在
Interlocked.Exchange
返回之前初始化值不可用,所以这可能没问题。

我只是假设我们正在谈论存储到

Interlocked.Exchange
相对两侧的两个不同对象,以及读取它们的读者没有任何
Volatile.Read
Interlocked.
操作,这是完全围栏。

(除了它的内存排序语义外,我对 C# 了解不多,这很有趣,因为它们是根据线程对缓存一致共享内存的访问排序进行记录的,这与 C++ 的形式主义仅在术语中定义创建 happens-before 关系和修改顺序。C# 的无锁原子语义甚至似乎已经从 MS 以 x86 为中心的历史中发展出来,就像所有

Interlocked.
RMW 都是完全障碍一样,这在 x86 上是不可避免的,但在其他 ISA 上需要额外成本.一些底层的排序概念在硬件级别上是非常通用的,除了强排序的 x86 更简单,不同的语言为它公开不同的抽象。)

© www.soinside.com 2019 - 2024. All rights reserved.