为什么我不能从Richter';s CLR通过C#

为什么我不能从Richter';s CLR通过C#,c#,volatile,C#,Volatile,我通过Richter的名著学习多线程,并编写了一个测试来研究和理解易失性行为(CLR via C#,第4版,第29章:原始线程同步构造,第764页)。里克特说,处理器可能会颠倒从加载到存储再到RAM操作的顺序,这可能会导致一些不可预测的行为。另外,我在网上读了很多文章,证实了Richter所描述的bug。然而,我的测试从未再现这种情况,而且总是失败。我的问题是:我做错了什么,为什么我不能复制这个bug 以下是我的处理器型号和构建设置: 核心i7-7700HQ(4个核心,超线程), 发布x86,在

我通过Richter的名著学习多线程,并编写了一个测试来研究和理解易失性行为(CLR via C#,第4版,第29章:原始线程同步构造,第764页)。里克特说,处理器可能会颠倒从加载到存储再到RAM操作的顺序,这可能会导致一些不可预测的行为。另外,我在网上读了很多文章,证实了Richter所描述的bug。然而,我的测试从未再现这种情况,而且总是失败。我的问题是:我做错了什么,为什么我不能复制这个bug

以下是我的处理器型号和构建设置: 核心i7-7700HQ(4个核心,超线程), 发布x86,在上优化flag(Richter断言这些设置可能导致错误),.NET 4.6.1,VS 2017,NUnit,ReSharper的测试UI

这是密码

[TestFixture]
公共类反向或反向测试
{
[测试]
公共无效已撤销或已撤销
{
ReversedOrderBug rob=新的ReversedOrderBug{AttemptsNumber=1000000};
bool hasBug=rob.Run();
True(hasBug);
}
}
公共类反向OrderBug
{
私人国际组织;
私人互联网;
//根据里希特的说法,处理器可以在A之前存储B。
//然而,根据MSND的说法,这种说法是错误的,因为CLR
//禁止颠倒顺序存储操作的顺序:
私有void Task1()
{
_A=1;
_B=1;
}
//在这里,我们试图重现_A=0和_B=1的时刻,
//以及读取操作的相反顺序(
//在_B之前读取_A的情况:
私有int Task2()
{
如果(_B==1)返回_A;
else-retutn-1;
}
///尝试复制错误的次数。
public int AttemptsNumber{get;set;}=int.MaxValue;
///复制“_A=0和_B=1”错误。
///如果捕获到错误,则为True,否则为False。
公营学校
{
//循环里克特书中的代码,直到得到_A=0和_B=1:
for(int i=0;itask1());
tasktask2=Task.Run(()=>task2());
Task.WaitAll(task1、task2);
//仅当_A=0和_B=1时才中断循环:
if(task2.Result==0)返回true;
}
//上一个循环无法捕获_A=0和_B=1的时刻:
返回false;
}
}
ReverseOrderBug类的修订版使用线程而不是任务,并添加了一个随机延迟来模拟异步性:

1) 第一个方法Run1使用new Thread()构造函数手动创建和运行线程。它的工作速度比ThreadPool或Tasks的类似程序慢得多。这是可以预测的,因为该方法会创建大量新线程,这会影响性能

2) 第二个Run2方法使用ThreadPool,速度要快得多,因为它将线程池中的线程排队,并仅在确实需要时创建新的线程

此外,我还在Task1和Task2方法中添加了一个RandomDelay调用。然而,结果仍然是一样的:测试总是失败。 我还应该提到,Task2方法中读取变量的手动反转使测试成功(首先读取A,然后读取B)。但是,我确信,反向阅读并不是这一成功的原因。相反,它表明CLR或处理器不会反转读取操作

修订后的守则如下:

public类ReversedOrderBug
{
私人国际组织;
私人互联网;
私有只读RandomNumberGenerator _Random=RandomNumberGenerator.Create();
私有无效随机延迟(bool longerDelay=true)
{
变量数据=新字节[2];
_Random.GetNonZeroBytes(数据);
变量编号=长延迟
?(数据[0]+数据[1])*100
:数据[0]*10;
而(数>0)数--;
}
//根据里希特的说法,处理器可以在A之前存储B。
//然而,根据MSND的说法,这种说法是错误的,因为CLR
//禁止颠倒顺序存储操作的顺序:
私有无效任务1CST(取消令牌源cst)
{
随机延迟();
_A=1;
_B=1;
cst.Cancel();
}
//在这里,我们试图重现_A=0和_B=1的时刻,
//以及读取操作的相反顺序(
//在_B之前读取_A的情况:
专用int Task2Cst(取消令牌源cst)
{
随机延迟();
变量结果=_B==1
?_A
: -1;
cst.Cancel();
返回结果;
}
///尝试复制错误的次数。
public int AttemptsNumber{get;set;}=int.MaxValue;
///复制“_A=0和_B=1”错误。手动创建线程。
///如果捕获到错误,则为True,否则为False。
公共bool Run1()
{
//循环里克特书中的代码,直到得到_A=0和_B=1:
for(int i=0;iTask1Cst(cst1));
var t2=新线程(()=>result=Task2Cst(cst2));
t1.Start();
t2.Start();
而(!(cst1.Token.IsCancellationRequested和&cst2.Token.IsCancellationRequested));
//仅当_A=0和_B=1时才中断循环:
if(result==0)返回true;
}
//前一个循环无法进行cat