volatile修饰词的作用
- 在阅读源码时,经常看到某些变量被
voltaile
修饰,不知道其作用,所以深入了解了一下 - 先说结果: 防止在release模式下编译时代码被优化,变量被缓存到寄存器中
- 这样的优化在多线程环境下可能会出现业务上的错误。
场景演示
namespace VolatileSample;
internal class Program
{
static bool _stop;
static void Main(string[] args)
{
Console.WriteLine("主线程开始");
var task = Task.Run(() =>
{
bool flag = false;
while (!_stop)
{
flag = !flag; // 必须有实际内容,并且要有实际意义(防止被优化)
// Thread.Sleep(10); // 不能添加Sleep
}
Console.WriteLine("测试线程终止");
});
Thread.Sleep(1000);
_stop = true;
task.Wait();
Console.WriteLine("主线程结束");
Console.ReadLine();
}
}
- 以上代码分别在
debug
和release
模式下运行,结果不一致,在release
模式下运行会被卡住(注意要点击exe运行,不要调试运行)
debug模式
-
以下是vs中debug模式下反汇编出来的代码
# while (!_stop) movzx ecx, byte ptr [7FF9326CE4ABh] test ecx, ecx sete cl # 这里简化以下 cmp ecx, 0 jne VolatileSample.Program+<>c.<Main>b__1_0()+02Dh (07FF93264823Dh) # 即 while循环中的代码 # Cnsole.WriteLine("测试线程终止"); mov rcx, ...
-
汇编代码解读
movzx ecx, byte ptr [7FF9326CE4ABh]
: 将_stop
值放到寄存器ecx
中test ecx, ecx
: 判断ecx
是否是0,如果是ZF
设置为1,否则设置为0
sete cl
: 将ZF
值赋值给cl
寄存器cmp ecx, 0
: 判断ecx
是否为0,如果是ZF
设置为1,否则设置为0
jne ...
: 如果ZF
是0,则跳转到指定位置,如果ZF
是1,则继续往下执行
-
可以看到,每次
while (!_stop)
判断的时候,都是从内存中读取值在做判断,所以执行逻辑没有问题,接下来再看看Release
模式下的汇编代码
release 模式
-
-
从上图中可以看出,程序进入了一个死循环,且没有任何判断条件,这就是 release模式下卡死的原因
-
汇编代码解读:
movcx eax, byte ptr[7FF92DCDE4A3h]
将_stop
值放到寄存器eax
中test eax, eax \ jne 00007FF92DC523F9
如果eax
为 1 ,则不执行while(){}
中的循环,从上下文看,这里的判断只会执行一次,要么不执行循环体,要么永远在循环体中出不来
解决办法
-
_stop
变量使用volatile
修饰:static volatile bool _stop;
-
-
以上汇编代码为加了
volatile
的release
模式代码,可以看出,while
的每次判断都是从内存中新取出的值进行判断,也就不存在卡死的问题