volatile关键字

7 篇文章 0 订阅

一、介绍

volatile是一个特征修饰符(type specifier),它的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。不光在C#中有volatile这个关键字,在C++和C中同样有。

编译器优化介绍:由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。

因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但是这样就有可能会读到脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)


二、从release中发现的问题

(一)、release对程序的优化

一般我们发布项目的时候通常都会采用release版本,因为release会在jit层面对我们的汇编代码进行优化,比如在迭代和内存操作的性能方面提升,下面我们先用一个简单的“冒泡排序”看下release和debug下面的性能差距。这个冒泡排序算法中会排序五万次数据,可以执行25亿次迭代。

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var rand = new Random();
            List<int> list = new List<int>();

            for (int i = 0; i < 50000; i++)
            {
                list.Add(rand.Next());
            }

            var watch = Stopwatch.StartNew();

            try
            {
                BubbleSort(list);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            watch.Stop();

            Console.WriteLine("耗费时间:{0}", watch.Elapsed);
        }

        //冒泡排序算法
        static List<int> BubbleSort(List<int> list)
        {
            int temp;
            //第一层循环: 表明要比较的次数,比如list.count个数,肯定要比较count-1次
            for (int i = 0; i < list.Count - 1; i++)
            {
                //list.count-1:取数据最后一个数下标,
                //j>i: 从后往前的的下标一定大于从前往后的下标,否则就超越了。
                for (int j = list.Count - 1; j > i; j--)
                {
                    //如果前面一个数大于后面一个数则交换
                    if (list[j - 1] > list[j])
                    {
                        temp = list[j - 1];
                        list[j - 1] = list[j];
                        list[j] = temp;
                    }
                }
            }
            return list;
        }
    }
}

Debug下面的执行效率:

Release下面的执行效率:

从上面两张图可以看到,debug所用时间是release所用时间的3倍还多,可以想象release对我们的程序的优化有多大了。

(二)、release应该注意的bug

release确实是一个非常好的东西,但是在享受好处的同时也不要忘了,任何优化都是要付出代价的,这世界不会什么好事都让你给占了。release有时候为了性能的提升,会大胆的给你做一些代码优化和cpu指令的优化,比如说把你的一些变量和参数缓存在cpu的高速缓存中,绝大多数情况下都不会遇到问题,但是遇到了就可能让你很头疼,因为只看代码逻辑发现不了问题在哪,比如下面这种:

class Program
    {
        static void Main(string[] args)
        {
            var isStop = false;

            var task = Task.Factory.StartNew(() =>
            {
                var isSuccess = false;

                while (!isStop)
                {
                    isSuccess = !isSuccess;
                }
            });

            Thread.Sleep(1000);
            isStop = true;
            task.Wait();

            Console.WriteLine("主线程执行结束!");
            Console.ReadLine();
        }
    }

这段代码意思很简单,就是先让线程休眠一秒,然后打印出"主线程执行结束!"这句话。但是正真执行的时候,却会出现下面的现象:

debug下正常输出,但是release下却一直在线程中死循环,没打印出"主线程执行结束!"。这就是因为release为了加快执行速度,mainthread和task将isStop变量从memory中加载到各自的cpu缓存中,而主线程执行isStop=true的时候而task读的还是cpu缓存中的脏数据,也就是还是按照isStop=false的情况进行执行,导致线程一直结束不了。


三、volatile关键字

那遇到这种问题怎么解决呢?这时候就要用到volatile关键字了,我们只需要把我们的isStop变量使用volatile关键字修饰就可以了:

class Program
{
    volatile static bool isStop = false;

    static void Main(string[] args)
    {
        var task = Task.Factory.StartNew(() =>
        {
            var isSuccess = false;

            while (!isStop)
            {
                isSuccess = !isSuccess;
            }
        });

        Thread.Sleep(1000);
        isStop = true;
        task.Wait();

        Console.WriteLine("主线程执行结束!");
        Console.ReadLine();
    }
}

这时再看release的执行结果: 

关键字volatile告诉编译器,jit,cpu不要对代码进行任何形式的优化,并且volatile修饰的变量必须从memory中读取,而不是cpu cache中。.net也提供了Thread.VolatileRead/VolatileWrite,这两个方法基本功能和volatile类似,都是为了解决CPU和编译器可能带来的指令乱序,和解决高速缓存一致性的问题。

在多线程环境下,多个线程对一个共享变量进行读写是一个很危险的操作,我们可以使用关键字volatile直接告诉编译器从内存中读取它修饰的变量。但是频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。

参考博客:

C语言中volatile关键字的作用_huhuandk的博客-CSDN博客_volatile关键字的作用

享受release版本发布的好处的同时也应该警惕release可能给你引入一些莫名其妙的大bug - 一线码农 - 博客园

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值