前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >volatile(1)

volatile(1)

作者头像
黑洞代码
发布2021-01-14 15:46:14
5160
发布2021-01-14 15:46:14
举报
文章被收录于专栏:落叶飞翔的蜗牛

volatile(1)

目录


1.volatile的作用

2.硬件系统架构

3.缓存一致性问题

4.缓存一致性协议

第1节 volatile的作用


1. volatile的作用是保证共享变量的可见性,不能保证原子性,也不能保证线程安全。

2. volatile的作用是确保所有线程在同一时刻读取到的共享变量的值是一致的。

3. 如果某个线程对volatile修饰的共享变量进行更新,那么其他线程可以立刻看到这个更新。

第2节 硬件系统架构


计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。

程序运行的数据是存储在主内存(通常说的内存)中,这时就会有一个问题:

读写主内存中的数据没有CPU中执行指令的速度快,

如果任何的交互都需要与主内存打交道则会大大降低CPU的效率(拖CPU的后腿)。

解决问题的办法:

CPU高速缓存的诞生。

CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。

现代CPU为了提高访问数据的效率,在每个CPU核心上都会有多级容量小,

速度快的缓存(分别称之为L1 cache,L2 cache,多核心共享L3 cache等),

用于缓存常用的数据。

缓存系统中是以缓存行(cache line)为单位存储的。缓存行是 2 的整数幂个连续字节,

一般为 32-256 个字节。最常见的缓存行大小是 64个字节。

因此当CPU在执行一条读内存指令时,

它是会将内存地址所在的缓存行大小的内容都加载进缓存中的。

也就是说,一次加载一整个缓存行。

CPU首先使用自己的寄存器,然后使用速度更快的L1缓存,其中:

L1D缓存数据;

L1I缓存指令;

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

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


CPU读取速度是够快了,问题是:CPU操作完成后,如何将数据写入?

缓存分布在每个CPU中,如何保障写入后的数据与各个CPU之间的缓存保持数据一致?

1. 直写(write-through)

直写是透过本级缓存,直接把数据写到下一级缓存(或直接到内存)中,如果对应的数据被缓存了,我们同时更新缓存中的内容(甚至直接丢弃)。所以,直写时缓存行永远和它对应的内存内容匹配。

2. 回写(write-back)

缓存不会立即把写操作传递到下一级,而是仅修改本级缓存中的数据,并且把对应的缓存数据标记为“脏”数据。脏数据会触发回写,也就是把里面的内容写到对应的内存或下一级缓存中。回写后,脏数据又变“干净”了。当一个脏数据被丢弃的时候,总是先要进行一次回写。

第3节 缓存一致性问题


举个栗子

i++;

当线程运行这段代码时

1. 首先会从主内存中读取i( i = 1)。

2. 然后复制一份到CPU高速缓存中,然后CPU执行 + 1 的操作。

3. 然后将数据+1后的结果写入到高速缓存中。

4. 最后将结果刷新到主存中。

其实这样做在单线程中是没有问题的,在多线程中是有问题。


假如有两个线程A、B都执行这个操作(i++),

按照我们正常的逻辑思维主内存中的i值应该是3,但事实是这样么?分析如下:

1. A、B两个线程从主存中读取i的值1到各自的高速缓存中。

2. 然后线程A执行+1操作并将结果写入高速缓存中,最后写入主内存中。

3. 此时主内存i==2,线程B做同样的操作,主内存中的i的值又被更新成2。

4. 所以A、B两个线程执行完毕后,最终结果为2并不是3。与我们预期值不等。

这种现象就是缓存一致性问题。

第4节 缓存一致性协议


解决缓存一致性方案有两种:

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

2.通过缓存一致性协议

方案1存在一个问题,它是采用一种独占的方式来实现的,即总线加LOCK#锁的话,只能有一个CPU能够运行,其他CPU都得阻塞,效率较为低下。

在多核CPU系统中,每个CPU核心都有自己的一级缓存、二级缓存等。这样一来当多个CPU核心在对共享的数据进行写操作时,就需要保证该共享数据在所有CPU核心中的可见性/一致性。


【窥探技术 + MESI协议】的出现,就是为了解决多核CPU时代,缓存不一致的问题的。

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

缓存本身是独立的,但是内存是共享资源,所有的内存访问都要经过仲裁(arbitrate):同一个指令周期中,只有一个缓存可以读写内存。

窥探技术的思想是,缓存不仅仅在做内存传输的时候才和总线打交道,而是不停地在窥探总线上发生的数据交换,跟踪其他缓存在做什么。

所以当一个缓存代表它所属的CPU去读写内存时,其他CPU都会得到通知,它们以此来使自己的缓存保持同步。只要某个CPU一写内存,其他CPU马上就知道这块内存在它们自己的缓存中对应的缓存行已经失效。

缓存系统操作的最小单位就是缓存行,而MESI是缓存行四种状态的首字母缩写,任何多核系统中的缓存行都处于这四种状态之一。

① 失效(Invalid)缓存行:该CPU缓存中无该缓存行,或缓存中的缓存行已经失效了。

② 共享(Shared)缓存行:缓存行的内容是同主内存内容保持一致的一份拷贝,在这种状态下的缓存行只能被读取,不能被写入。多组缓存可以同时拥有针对同一内存地址的共享缓存行。

③ 独占(Exclusive)缓存行:和S状态一样,也是和主内存内容保持一致的一份拷贝。

区别在于,如果一个CPU持有了某个E状态的缓存行,那其他CPU就不能同时持该内容的缓存行,所以叫“独占”。

这意味着,如果其他CPU原本也持有同一缓存行,那么它会马上变成“失效”状态(I状态)。

④ 已修改(Modified)缓存行:该缓存行已经被所属的CPU修改了。

如果一个缓存行处于已修改状态,那么它在其他CPU缓存中的拷贝马上会变成失效状态。

此外,已修改缓存行如果被丢弃或标记为失效(即,从M状态 ——> I状态),那么先要把它的内容回写到内存中 ———— 这和回写模式下常规的处理方式一样。

只有当缓存行处于E或M状态时,CPU才能去写它,也就是说只有这两种状态下,CPU是独占这个缓存行的。

当CPU想写某个缓存时,如果它没有独占权,它必须先发送一条“我要独占权”的请求给总线,这会通知其他CPU,把它们拥有的同一缓存行的拷贝失效(I状态)。

只有在获得独占权后,CPU才能开始修改数据。并且此时,这个CPU知道,这个缓存行只有一份拷贝,在我自己的缓存里,所以不会有任何冲突。

反之,如果有其他CPU想读取这个缓存行(我们马上能知道,因为我们一直在窥探总线),独占或已修改的缓存行必须先回到“共享”状态。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 落叶飞翔的蜗牛 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档