volatile

104 阅读2分钟

volatile关键字能确保多线程对共享变量的可见性,以及防止指令重排序。

volatile如何保证可见性?

通过汇编指令来查看volatile关键字修饰的共享变量在进行写操作的时候加了一个lock前缀的指令。而lock前缀的指令会引发两件事:

  1. 将当前处理器缓存行的数据写回系统主内存
  2. 写回主内存的操作会使其他处理器里缓存的该内存地址的数据无效

如果是单核CPU,就不存在使用volatile来保证共享变量的可见性,因为同一进程的不同线程是共享数据的,有共享的缓存区。但对于多核CPU来说,每个CPU缓存都是相互独立的,需要解决缓存一致性问题,所以通过主内存来进行通信解决缓存的数据同步问题。

lock#指令会引起处理器缓存回写主内存

  • 对于Intel486和Pentium处理器,多处理器环境中,lock#声言期间,处理器会锁住总线,其他CPU则不能访问系统内存。
  • P6和目前的处理器,如果访问的内存区域数据以及缓存在处理器上,不会声言lock#信号,而是锁定缓存,并回写到主内存,使用缓存一致性机制来保证修改的原子性,即只有一个处理器缓存会修改主内存数据。

一个处理器回写到主内存会导致其他处理器的缓存无效

  • IA32和Intel64处理器能嗅探其他处理器访问系统内存和它们的内部内存,通过嗅探技术来保证它的内部缓存、系统内存和其他处理器的缓存数据在总线上保持一致。
  • Pentium和P6处理器中一个处理器嗅探其他处理器打算写内存地址,这个地址当前处理共享状态,正在嗅探的处理器会将它的内存行失效,在下次访问相同内存地址时,强制执行缓存行填充。

volatile禁止指令重排序

从硬件层面来讲,指令重排序是指处理器采用了多条指令不按程序规定的顺序执行,而指令重排序是机器级别的优化。

主要是为了防止由于指令重排序导致的多线程并发执行的错误。比如先读取配置,再赋值标签为true,但由于指令重排序,标签先被赋值为true,若此刻有其他的线程在读取此标签进行配置操作,然而配置还未读取出来导致了错误。volatile关键字可避免这种情况发生。