这里需要内存屏障吗?

3

我有以下情况:

var tmp1 = new MyObject() { A = i, B = 2 };
// ** write barrier here??
this.Obj = tmp1;

另一个线程可以做类似这样的事情:

var tmp = this.Obj;
// ** read barrier here?? 
use(tmp.A);

像 'Obj' 这样的对象只需要编写一次,然后被多个线程(多次)读取。

我知道 Obj 在两个线程中均不为 null。我也不关心 'this.Obj' 的同步。我关心的是,一旦我读取了引用 tmp = Obj,那么内容(如 AB)也是有效的。

我的问题是:我是否需要在上面标记的位置设置内存屏障(例如 Thread.MemoryBarrier();)来确保这一点,还是这总是隐含的 OK?


人们似乎不喜欢这个问题。

我的问题源于以下情况。我已经阅读了有关内存栅栏的资料,它们保证:(引用)

执行当前线程的处理器不能以使内存访问在调用 MemoryBarrier 之前执行,而在调用 MemoryBarrier 之后执行的方式重新排序指令。

如果你看一下代码,CPU / 编译器可能能够重新编写代码:

var tmp1 = new MyObject();
tmp1.A = i;
tmp1.B = 2;
this.Obj = tmp1;

更糟糕的是:

var tmp1 = new MyObject();
this.Obj = tmp1;
tmp1.A = i;
tmp1.B = 2;

如果另一个线程取走了最后的案例,它可以从内存中读取this.Obj,而AB仍然具有默认值。
请注意,这不仅仅是编译器能够重新排序的问题;这也涉及到CPU允许重新排序的问题。
换句话说:(感谢@MattBurland)是否保证对象初始化器在将tmp1分配给this.Obj之前运行?还是我需要使用内存栅栏来手动确保这一点?

1
@HenkHolterman this 不是关键字。我更关心的是您应该在何时使用这些概念(我已经阅读了大量的互联网信息,这是我理解应该如何使用它们,但我仍然觉得很困惑)。至于C ++的备注 - 我对概念感兴趣,这应该适用于任何语言...据我所知,这些概念是'read','write'和'full' fences,其中Thread.MemoryBarrier是一个完整的fence。 - atlaste
1
如果代码只被写了一次,那么这里实际上并没有问题。除非你担心 this.Obj 是可变的,并且读取线程可能会更改 A 和/或 B。但是你说你不关心同步问题。 - Matt Burland
1
你是想保护MyObject内容吗?那么你应该从对象内部寻找解决方法,例如通过将其设置为只读或不可变。 - Panagiotis Kanavos
好的,问题是:在将tmp1赋值给this.Obj之前,对象初始化程序是否保证会运行。是这样吗?我希望是这样,但实际上我并不确定。 - Matt Burland
1
请注意,您不需要完整的屏障...半个栅栏就足够了。 如果this.Obj是一个字段,则可以使用Volatile.Write(this.Obj, tmp1); 如果不是,则可以拥有一个static volatile int _barrier;,然后执行:_barrier = 0; this.Obj = tmp1; - xanatos
显示剩余9条评论
1个回答

1

C#规范只保证重排序不会影响当前线程所看到的内容。因此,似乎JIT可以自由地重新排列下面的2-4操作,因为这并不影响生产者线程的行为:

  1. 创建一个新的MyObject
  2. i赋值给成员A
  3. 2分配给成员B
  4. 将新对象分配给this.Obj

因此,在步骤3和4之间需要一个屏障。另一种选择是使this.Obj成为volatile。这将确保在写入this.Obj后不允许移动其他读取或写入,并强制执行所需的顺序。


谢谢,这就是我一直在寻找的信息。只是想知道,你特别提到了“创建”;如果我使用构造函数而不是初始化器,会有什么区别吗? - atlaste
我不是100%确定,但我怀疑在理论上它们是等价的。 在实践中,我怀疑构造函数可能会意外解决排序问题,仅仅因为它可能没有被内联。 如果它被内联了,指令序列是相同的,因此会遭受相同的重新排序潜在问题。 - Dark Falcon
好的,只是为了确认一下。这意味着在这种情况下永远不需要读取屏障,因为它不能被重新排序以导致问题。在3和4之间的写入屏障或易失性(还没有考虑过)将足够。正确吗? - atlaste
这是正确的。请注意,如果您只使用屏障,您的读者可能会在本地缓存读取。如果您真的关心对象是相对较新的,请使用锁定或使用volatile。无论如何,屏障和volatile都将确保读者永远不会看到不完整的对象。 - Dark Falcon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接