这里需要内存围栏吗?
本文关键字:内存 这里 | 更新日期: 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
,内容(例如A
和B
)也是有效的。
我的问题是:我是否需要在上述标记位置的内存屏障(例如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
,而A
和B
仍然具有默认值。
请注意,这不仅仅是编译器能够重新排序的问题;这也是一个允许CPU重新排序的问题。
换句话说:(谢谢@MattBurland)
是否保证对象初始化器将在tmp1分配给this.Obj
之前运行?或者我是否需要使用内存围栏来手动确保这一点?
c#规范只保证重新排序不会影响当前线程看到的内容。因此,JIT似乎可以自由地重新排序下面2-4个操作,因为它不影响生产者线程的行为:
- 新建
MyObject
- 将
i
分配给成员A
- 将
2
分配给成员B
- 将新对象分配给
this.Obj
因此,在步骤3和步骤4之间似乎需要一个屏障。另一个选择是让this.Obj
成为volatile
。这将确保在写入this.Obj
之后不允许移动其他读或写操作,同时强制执行所需的顺序。