并发编程系列(五)volatile关键字详解

volatile的作用

  1. volatile的作用是保证共享变量的可见性,不能保证原子性,也不能保证线程安全(但是对于单个共享变量的读,写是原子性的)
  2. 可见性:如果某个线程对volatile修饰的共享变量进行更新,那么其他线程可以立刻看到这个更新;
  3. 有序性:禁止指令重排序

在多线程并发编程中sychronized和volatile都扮演着重要的角色,volatile扮演者重要的角色,volatile是轻量级的synchronized,在多处理器开发中保证了共享变量的 “可见性“” 。可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值,如果volatile变量修饰符使用恰当的话,他比synchronized的使用和执行成本更低,因为他不会引起线程上下文的切换和调度


cpu与内存交互

CPU避免和主内存直接打交道,与速度比主内存高出很多的CPU高速缓冲区打交道,充分利用了CPU性能

cpu首先使用自己的寄存器,然后使用速度更快的L1缓存,其中L1D缓存数据;L1l缓存指令;L1缓存和次快的L2做同步数据;

L2缓存和L3按照缓存做同步数据(L2和按照内核数据量做了等分,分给各个内核使用)L3和主内存同步数据

缓存一致性问题

比如说在执行i++

当线程运行这段代码时:

  1. 首先会从主内存读取i(i=1)
  2. 然后复制一份到CPU高速缓存中,CPU在执行+1的操作
  3. 然后将数据+1后的结果写入到高速缓存中
  4. 最后将结果刷新到主存中

这种操作在单线程中没有问题,但是在多线程中会出现问题

如果有两个线程同时在进行这个操作(i++)

  1. Thread1 和Thread2两个线程从主存中读取i的值到各自的高速缓存中,
  2. 然后Thread1执行+1操作并将结果写入到高速缓存中,最终写入到主内存中
  3. 此时主内存i==2,线程Tread2也在做同样的操作,主内存的i值又被更新成2
  4. 所以Thread1和Thread2两个线程执行完毕后,最终结果为2并不是3,与我们的预期值不等

这就是缓存一致性问题

缓存一致性解决方案

1:通过在总线加LOCK#锁的方式:

这是采用了一直这种独占的方式来实现的,即总线加LOCK#锁的话,只能有一个CPU能够运行,其他CPU都要阻塞,来自其他CPU或者总线代理的控制请求将会阻塞,效率很低(其他CPU会出现假死的情况)。

2:缓存一致性协议【嗅探技术+MESI协议】

“嗅探” 的基本思想就是在所有内存传输都发生在一条共享的总线上,所有的CPU都能看到这总线。

缓存本身是独立的,但是内存是共享资源的,所有内存访问都要经过仲裁(arbitrate):同一个指令周期中,只有一个缓存可以读写内存,嗅探的技术思想就是,缓存不仅仅在做内存传输的时候才和总线打交道,而是不停的在窥探总线上发生的数据交换,跟踪其他缓存在做什么,所以当一个缓存代表他所属的CPU去读写内存时,其他CPU都会得到通知,他们以此来使自己的缓存保持同步,只要某个cpu一写内存,其他CPU马上就知道这块内存在他们的缓存中对应的缓存行已经失效

MESI协议:

1、修改(Modified)高速缓存行仅存在于当前高速缓存中,并且是脏的 - 它已从主存储器中的值修改(M状态)。在允许对(不再有效)主存储器状态的任何其他读取之前,需要高速缓存在将来的某个时间将数据写回主存储器。回写将该行更改为共享状态(S)。

2、独家(Exclusive)缓存行仅存在于当前缓存中,但是干净 - 它与主内存匹配。它可以随时更改为共享状态,以响应读取请求。或者,可以在写入时将其改变为修改状态。

3、共享(Shared)表示此高速缓存行可能存储在计算机的其他高速缓存中并且是干净的 - 它与主存储器匹配。可以随时丢弃该行(更改为无效状态)。

4、无效(Invalid)表示此缓存行无效(未使用)

 

volatile原理依赖底层硬件协议(MESI)的实现

1

只会锁定工作内存

通过总线裁决保证只有一个变量能够被修改成功

 

发布了55 篇原创文章 · 获赞 3 · 访问量 5256

猜你喜欢

转载自blog.csdn.net/qq_38130094/article/details/103448564
今日推荐