基本上我有以下情况:
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
,内容(例如A
和B
)也是有效的。
我的问题是:我是否需要在上述标记位置上设置内存障碍(例如Thread.MemoryBarrier();
)以确保或者总是隐含的?
似乎人们不喜欢这个问题。
我的问题来自以下内容。我已经读过内存栅栏,他们保证:(引用)
执行当前线程的处理器无法重新排序指令,使得在调用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
,而A
和B
仍然具有默认值。
请注意,这不仅仅是编译器能够重新排序的问题;它也是允许CPU重新排序的问题。
换句话说:(感谢@MattBurland)
是否可以保证在将tmp1分配给this.Obj
之前运行对象初始值设定项?或者我是否需要使用内存栅栏来手动确保?
答案 0 :(得分:1)
C#规范只保证重新排序不会影响当前线程看到的内容。因此,似乎JIT可以自由地重新排序下面的操作2-4,因为它不会影响生产者线程的行为:
MyObject
i
分配给成员A
2
分配给成员B
this.Obj
因此,步骤3和4之间似乎需要屏障。另一种选择是制作this.Obj
volatile
。这将确保在写入this.Obj
之后不允许移动其他读取或写入,同时强制执行所需的排序。