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();
    }
}
  • 以上代码分别在 debugrelease 模式下运行,结果不一致,在 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;

  • 以上汇编代码为加了 volatilerelease模式代码,可以看出,while 的每次判断都是从内存中新取出的值进行判断,也就不存在卡死的问题

posted on 2025-02-12 22:20  baby-jie  阅读(36)  评论(0)    收藏  举报