在讨论CPU的内存屏障之前,让我们先了解一下缓存结构。
缓存(Cache)结构简介
现代计算机系统的缓存结构粗略如下:
每个CPU都有自己的缓存。
缓存(Cache)分为又分多个级别。
一级缓存L1的访问非常接近一个cpu周期(cycles),二级缓存L2的存取可能就要大概10个周期了。
缓存和内存交换数据的最小单元叫Cache Line。它是一个固定长度的块,可能是16到256字节(bytes).
比如一个32位的CPU有1M的缓存,每个Cache Line的大小是64bytes。那么这个缓存就可以有1M*1024*1024/64=16384条Cache Line(s) 。
CPU重排序
CPU以CacheLine为单位,通过Load,Store 和Write-combining等动作: 操作和重拍序缓存。
如果你对SourceControl熟悉:
那么请把内存想象成Source Control的一个仓库。
而每个CPU相当以一个程序员。
而缓存是每个程序员对仓库的一个本地副本。
以此假设:有张三和李四两个程序员,发生如下场景:
X,Y是是仓库里面的两个文件,最初他们的值都是0,张三和李四做了如下操作:
张三在他的副本中(相当于缓存)中更改了X文件的值为1:
Store X 1
Load r1 Y
李四在自己的副本中更改了Y文件的值为1:
Store Y 1
Load r2 Y
就会出现下面的情况,CPU0是张三,李四是CPU1:
结果是:张三的Y文件和李四的X文件都不是最新的。
那么在解决张三和李四的问题之前,请看官们耐心看完下面的四个屏障介绍,如果对四个屏障的Store和Load指令不理解,请移步我的前两篇文章:
CPU的指令分类 和 内存屏障(Memory Barriers)--Compile Time
细说四种内存屏障
这四种屏障也是最接近真实CPU实现的屏障指令。
注意,所有的内存屏障都有防止重排序功能。
即禁止CPU把前面的Load/Store操作排序到屏障后面的Load/Store后面
以LoadLoad为例:它禁止前面的Load(s)操作排序到后面的Load(s)操作之后,其他几个内存屏障一样,不做赘述。
- LoadLoad
屏障之前的Load(s) 操作完才能进行屏障后面的Load(s)。
这个很类似张三必须对前面的文件从仓库中进行pull下来,他才能进行后面的文件pull。
这样就保证了张三在进行下面的操作之时,屏障前面的文件是最新的(不能一直最新,因为李四还可能在仍然在改文件)
- StoreStore相当于先把屏障前面的文件push到仓库,才能进行后面文件的push操作。
这样就防止了我上一篇文章的多线程问题,如果没看过请移步内存屏障(Memory Barriers)--Compile Time。
- LoadStore
LoadStore 张三必须把前面的pull下来,才能push文件到仓库。主要防止后面的文件改动依赖前面的值
- StoreLoad
StoreLoad 张三必须push文件到仓库以便于让李四可以看见(Visible),然后才能从仓库获取文件。
解决问题
我想你应该知道答案了:
张三:
Store X 1
StoreLoad Barrier
Load r1 Y
LoadLoad Barrier 或者 LoadStore Barrier
李四:
Store Y 1
StoreLoad Barrier
Load r2 Y
LoadLoad Barrier 或者 LoadStore Barrier
这样他们的更新保证对方看见,也及时查看对方的更新。
更多干货,请长按下图关注IT圈圈。