Nutshell中的C#4(强烈推荐的btw)使用以下代码来演示MemoryBarrier的概念(假设A和B在不同的线程上运行):
class Foo{
int _answer;
bool complete;
void A(){
_answer = 123;
Thread.MemoryBarrier(); // Barrier 1
_complete = true;
Thread.MemoryBarrier(); // Barrier 2
}
void B(){
Thread.MemoryBarrier(); // Barrier 3;
if(_complete){
Thread.MemoryBarrier(); // Barrier 4;
Console.WriteLine(_answer);
}
}
}
Run Code Online (Sandbox Code Playgroud)
他们提到障碍1和4阻止这个例子写0和障碍2和3提供新鲜度保证:他们确保如果B在A之后运行,读_complete将评估为真.
我不是真的得到它.我想我明白为什么壁垒1和4是必要的:我们不想在写_answer进行优化,并放置在写后_complete(屏障1),我们需要确保_answer没有被缓存(光栅4) .我也认为我理解为什么Barrier 3是必要的:如果A在写完_complete = true之后才运行,B仍然需要刷新_complete以读取正确的值.
我不明白为什么我们需要障碍2!我的一部分说这是因为可能线程2(运行B)已经运行直到(但不包括)if(_complete),因此我们需要确保_complete被刷新.
但是,我不知道这有多大帮助.是不是仍然可以在A 中将_complete设置为true但是B方法会看到_complete的缓存(错误)版本?即,如果线程2运行方法B直到第一个MemoryBarrier之后,然后线程1运行方法A直到_complete = true但没有进一步,然后线程1恢复并测试是否(_complete) - 如果不导致错误 …
c# multithreading thread-safety shared-memory memory-barriers
我花了很多周时间在C#4.0中进行多线程编码.但是,有一个问题对我来说仍然没有答案.
我知道volatile关键字阻止编译器将变量存储在寄存器中,从而避免无意中读取过时值.写入在.Net中总是不稳定的,因此任何说明它也避免了stales写入的文档都是多余的.
我也知道编译器优化有些"不可预测".以下代码将说明由于编译器优化而导致的停顿(在VS之外运行发布编译时):
class Test
{
public struct Data
{
public int _loop;
}
public static Data data;
public static void Main()
{
data._loop = 1;
Test test1 = new Test();
new Thread(() =>
{
data._loop = 0;
}
).Start();
do
{
if (data._loop != 1)
{
break;
}
//Thread.Yield();
} while (true);
// will never terminate
}
}
Run Code Online (Sandbox Code Playgroud)
代码表现如预期.但是,如果我取消注释//Thread.Yield(); 行,然后循环将退出.
此外,如果我在do循环之前放入Sleep语句,它将退出.我不明白.
当然,用volatile来装饰_loop也会导致循环退出(以其显示的模式).
我的问题是:编译器遵循的规则是什么,以确定何时隐含执行易失性读取?为什么我仍然可以通过我认为奇怪的措施退出循环?
编辑
IL代码如图所示(档位):
L_0038: ldsflda valuetype ConsoleApplication1.Test/Data ConsoleApplication1.Test::data
L_003d: ldfld int32 ConsoleApplication1.Test/Data::_loop
L_0042: ldc.i4.1
L_0043: beq.s L_0038 …Run Code Online (Sandbox Code Playgroud)