了解 CLR 2.0 内存模型

发布于 2024-09-03 17:26:10 字数 1641 浏览 8 评论 0原文

Joe Duffy 给出了描述 CLR 2.0+ 内存模型的 6 条规则 (这是实际的实现,而不是任何 ECMA 标准)我正在写下我试图解决这个问题的尝试,主要是作为一种橡皮鸭的方式,但如果我在逻辑上犯了错误,至少这里有人能够抓住在它给我带来悲伤之前。

  • 规则 1:负载之间的数据依赖性 并且商店从未受到侵犯。
  • 规则 2:所有存储都有发布语义, 即之后没有负载或存储可以移动 一。
  • 规则 3:所有易失负载均 获取,即不能加载或存储 移动到一个之前。
  • 规则 4:无负载且 商店可能会跨越完整的障碍 (例如Thread.MemoryBarrier、锁 获取、联锁、交换、 Interlocked.CompareExchange 等)。
  • 规则 5:加载并存储到堆 可能永远不会被介绍。
  • 规则 6: 加载和存储只能被删除 当合并相邻负载时并且 商店往返于同一地点。

我正在尝试理解这些规则。

x = y
y = 0 // Cannot move before the previous line according to Rule 1.

x = y
z = 0
// equates to this sequence of loads and stores before possible re-ordering
load y
store x
load 0
store z

从这个角度来看,负载 0 似乎可以移动到负载 y 之前,但存储可能根本无法重新排序。因此,如果一个线程看到 z == 0,那么它也会看到 x == y。

如果 y 是不稳定的,则负载 0 不能在负载 y 之前移动,否则可以。易失性商店似乎没有任何特殊属性,没有商店可以相互重新排序(这是一个非常有力的保证!)

完整的障碍就像沙子里的一条线,装载和商店不能移动超过。

不知道规则5是什么意思。

我猜规则 6 的意思是,如果您这样做:

x = y
x = z

那么 CLR 可能会同时删除对 y 的加载和对 x 的第一个存储。

x = y
z = y
// equates to this sequence of loads and stores before possible re-ordering
load y
store x
load y
store z
// could be re-ordered like this
load y
load y
store x
store z
// rule 6 applied means this is possible?
load y
store x // but don't pop y from stack (or first duplicate item on top of stack)
store z

如果 y 不稳定怎么办?我在规则中没有看到任何禁止执行上述优化的内容。这并不违反双重检查锁定,因为两个相同条件之间的 lock() 会阻止负载移动到相邻位置,并且根据规则 6,这是唯一可以消除负载的时间。

所以我想我理解除了规则 5 之外的所有内容。任何人都想启发我(或纠正我或为上述任何内容添加一些内容?)

Joe Duffy, gives 6 rules that describe the CLR 2.0+ memory model (it's actual implementation, not any ECMA standard) I'm writing down my attempt at figuring this out, mostly as a way of rubber ducking, but if I make a mistake in my logic, at least someone here will be able to catch it before it causes me grief.

  • Rule 1: Data dependence among loads
    and stores is never violated.
  • Rule 2: All stores have release semantics,
    i.e. no load or store may move after
    one.
  • Rule 3: All volatile loads are
    acquire, i.e. no load or store may
    move before one.
  • Rule 4: No loads and
    stores may ever cross a full-barrier
    (e.g. Thread.MemoryBarrier, lock
    acquire, Interlocked.Exchange,
    Interlocked.CompareExchange, etc.).
  • Rule 5: Loads and stores to the heap
    may never be introduced.
  • Rule 6:
    Loads and stores may only be deleted
    when coalescing adjacent loads and
    stores from/to the same location.

I'm attempting to understand these rules.

x = y
y = 0 // Cannot move before the previous line according to Rule 1.

x = y
z = 0
// equates to this sequence of loads and stores before possible re-ordering
load y
store x
load 0
store z

Looking at this, it appears that the load 0 can be moved up to before load y, but the stores may not be re-ordered at all. Therefore, if a thread sees z == 0, then it also will see x == y.

If y was volatile, then load 0 could not move before load y, otherwise it may. Volatile stores don't seem to have any special properties, no stores can be re-ordered with respect to each other (which is a very strong guarantee!)

Full barriers are like a line in the sand which loads and stores can not be moved over.

No idea what rule 5 means.

I guess rule 6 means if you do:

x = y
x = z

Then it is possible for the CLR to delete both the load to y and the first store to x.

x = y
z = y
// equates to this sequence of loads and stores before possible re-ordering
load y
store x
load y
store z
// could be re-ordered like this
load y
load y
store x
store z
// rule 6 applied means this is possible?
load y
store x // but don't pop y from stack (or first duplicate item on top of stack)
store z

What if y was volatile? I don't see anything in the rules that prohibits the above optimization from being carried out. This does not violate double-checked locking, because the lock() between the two identical conditions prevents the loads from being moved into adjacent positions, and according to rule 6, that's the only time they can be eliminated.

So I think I understand all but rule 5, here. Anyone want to enlighten me (or correct me or add something to any of the above?)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

一萌ing 2024-09-10 17:26:10

Joe Duffy 在 Windows 上的并发编程 的 pp517-18 上讨论了规则 5:

作为负载可能出现的情况的示例
引入了,考虑这段代码:

MyObject mo = ...;
int f = mo.field;
if (f == 0)
{
    // do something
    Console.WriteLine(f);
}

如果初始之间的时间段
将 mo.field 读入变量 f 并
随后在中使用 f
Console.WriteLine 足够长,
编译器可能会决定它会更多
重读 mo.field 两遍很有效。
...这样做会出现问题,如果
mo 是一个堆对象,线程是
同时写信给mo.field。这
if 块可能包含假设的代码
读入 f 的值仍为 0,并且
read 的引入可能会破坏
这个假设。此外
禁止此操作
变量,.NET 内存模型
禁止对普通变量使用它
也指 GC 堆内存。

在博客中介绍了一个重要的地方:提高的标准模式一个事件。

EventHandler handler = MyEvent;
if (handler != null)
    handler(this, EventArgs.Empty);

为了防止在单独线程上删除事件处理程序时出现问题,我们读取 MyEvent 的当前值,并且仅在该委托非空时调用事件处理程序。

如果可以引入从堆读取,编译器/JIT 可能会决定再次读取 MyEvent,而不是使用本地,这会引入竞争条件。

Joe Duffy discusses Rule 5 on pp517-18 of Concurrent Programming on Windows:

As an example of when a load might be
introduced, consider this code:

MyObject mo = ...;
int f = mo.field;
if (f == 0)
{
    // do something
    Console.WriteLine(f);
}

If the period of time between the initial
read of mo.field into variable f and
the subsequent use of f in the
Console.WriteLine was long enough, a
compiler may decide it would be more
efficient to reread mo.field twice.
... Doing this would be a problem if
mo is a heap object and threads are
writing concurrently to mo.field. The
if-block may contain code that assumes
the value read into f remained 0, and
the introduction of reads could break
this assumption. In addition to
prohibiting this for volatile
variables, the .NET memory model
prohibits it for ordinary variables
referring to GC heap memory too.

I blogged about one important place where this matters: the standard pattern for raising an event.

EventHandler handler = MyEvent;
if (handler != null)
    handler(this, EventArgs.Empty);

In order to prevent problems with removing an event handler on a separate thread, we read the current value of MyEvent and only invoke the event handlers if that delegate is non-null.

If reads from the heap could be introduced, the compiler/JIT might decide that it could be better to read MyEvent again, rather than using the local, which would introduce a race condition.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文