这里需要内存围栏吗?

本文关键字:内存 这里 | 更新日期: 2023-09-27 18:04:24

基本上我有以下情况:

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在两个线程中都不会为空;我也不关心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;

如果另一个线程拾取最后一个case,它可以从内存中读取this.Obj,而AB仍然具有默认值。

请注意,这不仅仅是编译器能够重新排序的问题;这也是一个允许CPU重新排序的问题。

换句话说:(谢谢@MattBurland)

是否保证对象初始化器将在tmp1分配给this.Obj之前运行?或者我是否需要使用内存围栏来手动确保这一点?

这里需要内存围栏吗?

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

  1. 新建MyObject
  2. i分配给成员A
  3. 2分配给成员B
  4. 将新对象分配给this.Obj

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