staticintc=0;
staticvoidThread2()
{
while(true)
{
c+=b;
varlocalA=a;
c+=b;
varlocalB=b;
if(localA>localB)
{
Console.WriteLine($"a={localA}b={localB}");
}
}
}
再编写主程序,启动上述的两个线程。
staticvoidMain(string[]args)
{
Task.Run(Thread1);
Task.Run(Thread2);
Console.ReadKey();
}
使用Debug配置,编译并运行该程序,命令行是没有输出的,符合我们的预期。但是使用Release配置的话,就会出现大量输出,其中a的值比b大1到5不等。
查看反汇编可以看到,在第1个c+=b语句处,程序将b的值放到了寄存器中,而后面的语句均使用了该寄存器内存放的值。所以,编译器实际上将对b的读取操作合并并且前置了。以下为反汇编结果片段。
00007FFB628A394Dmovrcx,7FFB6292FBD0h
00007FFB628A3957movedx,1
00007FFB628A395Ccall00007FFBC2387B10
00007FFB628A3961movesi,dwordptr[7FFB6292FC08h]
00007FFB628A3967movecx,esi
00007FFB628A3969addecx,dwordptr[7FFB6292FC0Ch]
00007FFB628A396Fmovdwordptr[7FFB6292FC0Ch],ecx
varlocalA=a;
00007FFB628A3975movedi,dwordptr[7FFB6292FC04h]
c+=b;
00007FFB628A397Baddecx,esi
c+=b;
00007FFB628A397Dmovdwordptr[7FFB6292FC0Ch],ecx
if(localA>localB)
00007FFB628A3983cmpedi,esi
00007FFB628A3985jle00007FFB628A394D
理论分析
在C#语言标准的Basicconcepts一章Executionorder一节(参见:Basicconcepts–C#languagespecification)中,提到了C#的执行顺序规范。C#程序的副作用在以下关键点处的顺序是被保留的:
- 对volatile字段的读写
- lock语句
- 线程的创建和终结
C#程序的执行顺序在满足以下条件的情况下,可以由执行环境任意调整的:
- 在同一线程内,数据的的依赖关系是被保留的。即,结果与语句按照顺序执行的情况一致。
- 初始化顺序的规则是被保留的。
- 相对于volatile字段的读写,副作用的顺序是被保留的。
而上述的副作用包括:
- 读取或写入volatile字段
- 写入非volatile变量
- 写入外部资源
- 抛出异常
由此可以推出,C#程序中对非volatile变量的读取顺序可能会被调整。在只有一个线程对该变量进行操作时,这个顺序的调整是保证不会影响结果的;但如果同时有其他的线程正在对变量进行修改,则读取的顺序是无法确定的。
因此,如果有多个线程同时访问的,对值的实时性有要求的变量,应当设置为volatile变量。将上述实验中的静态变量a和b改为volatile变量后,即使是Release配置下,也不会出现命令行的输出,即两个变量的读取顺序符合原始的语句顺序。
结论
在C#程序中,读取非volatile变量的顺序可能被执行环境任意调整。如果某个变量在被读取的时候会被其他线程写入,为了该读取结果的实时性,应当将该变量设置为volatile变量。
总结
到此这篇关于关于C#执行顺序带来的一些潜在问题就介绍到这了,更多相关C#执行顺序潜在问题内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。