1:什么是volatile?
在C语言中,volatile
关键字同样用于修饰变量,volatile
告诉编译器该变量的值可能会在程序的控制之外被改变,因此编译器在优化代码时不能对该变量的访问进行优化,比如不能将其缓存到寄存器中,而是每次访问时都需要直接从内存中读取其值。
2:变量的访问原理。
1,读变量;
内存->寄存器(CPU)
2,写变量;
寄存器(CPU)->内存
正常变量的访问应该是这个样子;
但是在计算机工作时,内存的访问速度不及CPU的处理速度,为了提升计算机的整体性能,在软硬件层面都有相应的优化机制。
1,硬件层面
引入高速缓冲(cache)。
2,软件层面
(1)编码优化(程序员)。
(2)编译优化(编译器)。
3:volatile的作用。
volatie的意思是“易变的”,在C语言中使用volatile修饰一个变量时,表示这个变量的值随时都有可能发生改变。因此编译器在编译的时候对该变量的存取操作不能进行优化,即告诉编译器每次存取该变量的时候都要从内存中去存取而不是使用之前在寄存器中的备份。
C语言中volatile
的主要用途包括:
-
内存映射的硬件寄存器:在嵌入式系统或系统编程中,经常需要通过C语言直接访问硬件寄存器。这些寄存器的值可能会由硬件事件(如中断,ADC采集等)或其他并发运行的程序部分意外地改变。在这种情况下,使用
volatile
可以确保每次访问寄存器时都能从硬件地址中读取最新的值,而不是从可能被编译器缓存的旧值中读取。 -
多线程环境中的共享变量:虽然C语言标准本身并不直接支持多线程(这是由操作系统或第三方库提供的),但在多线程程序中,使用
volatile
可以确保一个线程对共享变量的修改能够立即对其他线程可见。然而,volatile
并不保证操作的原子性,也不提供互斥访问,因此在多线程程序中通常需要与适当的同步机制(如互斥锁)结合使用。 -
信号处理器中的变量:在C语言中,可以使用信号处理器来响应硬件或软件事件(如中断)。在信号处理器中访问的变量应该被声明为
volatile
,因为信号处理器可能在主程序的执行流之外被调用,从而改变这些变量的值。
4:使用volatile关键字的优缺点
优点
-
保证可见性:
当一个线程修改了volatile变量的值,这个新值对其他线程来说是立即可见的。这是通过禁止指令重排序和立即刷新到主内存中实现的。这避免了在多线程环境下,一个线程修改了共享变量的值,而另一个线程却读取不到最新值的问题。 具体来说,当一个线程修改了volatile变量的值时,该线程会将该变量的值立即写入主内存中,并通知其他线程该变量已经发生了修改。其他线程在读取该volatile变量时,会从主内存中重新读取最新的值,而不是从本地工作内存中读取。 -
保证有序性:
volatile关键字可以禁止指令重排序,从而保证对volatile变量的操作是有序的。当对一个volatile变量进行读/写操作时,该操作之前的代码必须已经执行完成,并且结果对后续的操作可见。这有助于避免多线程环境下可能出现的因指令重排序而导致的错误。 -
轻量级同步:
相对于其他同步机制(如synchronized),volatile是一种轻量级的同步机制。它不需要通过锁机制来同步对共享变量的访问,从而减少了因锁竞争而导致的性能开销。
缺点
-
不保证原子性:
虽然volatile可以保证对变量的操作具有可见性和有序性,但它并不能保证操作的原子性。例如,对于i++这样的操作,它实际上包含了读取、修改和写入三个步骤,而volatile只能保证这三个步骤的可见性和有序性,但不能保证它们作为一个整体是原子的。因此,在多线程环境下,如果需要对变量进行复合操作(如自增、自减等),仅使用volatile是不够的,还需要使用其他同步机制(如synchronized或原子类)来保证操作的原子性。 (**原子性(Atomicity)**是指一个或多个操作要么全部执行成功,要么全部执行失败,在执行过程中不会被任何因素打断的特性。在并发编程中,原子性是一个非常重要的概念,它用于保证多线程环境下对共享数据的访问是安全的。) -
性能开销:
虽然volatile相对于其他同步机制来说是一种轻量级的同步方式,但它仍然会对性能产生一定的影响。这是因为volatile变量在每次被线程访问时,都需要从主内存中读取最新的值,并且在修改后需要将新值写回主内存。这种频繁的内存访问操作会增加CPU的缓存一致性开销和内存带宽消耗。 -
使用限制:
volatile的使用场景相对有限。它主要适用于那些被多个线程读取,但只有一个线程写入(或者写入操作不依赖于当前值)的共享变量。如果变量的写入操作依赖于其当前值,或者多个线程同时写入同一个变量,那么仅使用volatile是不够的,还需要结合其他同步机制来保证线程安全。