黑帽子技术

黑帽子技术 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

微信搜索公众号【黑帽子技术】IT技术干货,有趣的科技内容

个人动态

黑帽子技术 发布了文章 · 2020-04-05

内存屏障(Memory Barrier)究竟是个什么鬼?

读者朋友你好hello

在开始阅读之前我们假设读者已经掌握了缓存一致性协议的MESI相关知识。如果没有建议阅读 带你了解缓存一致性协议 MESI


问题的产生

如上图 CPU 0 执行了一次写操作,但是此时 CPU 0 的 local cache 中没有这个数据。于是 CPU 0 发送了一个 Invalidate 消息,其他所有的 CPU 在收到这个 Invalidate 消息之后,需要将自己 CPU local cache 中的该数据从 cache 中清除,并且发送消息 acknowledge 告知 CPU 0。CPU 0 在收到所有 CPU 发送的 ack 消息后会将数据写入到自己的 local cache 中。这里就产生了性能问题:当 CPU 0 在等待其他 CPU 的 ack 消息时是处于停滞的(stall)状态,大部分的时间都是在等待消息。为了提高性能就引入的 Store Buffer。


Store Buffer

store buffer 的目的是让 CPU 不再操作之前进行漫长的等待时间,而是将数据先写入到 store buffer 中,CPU 无需等待可以继续执行其他指令,等到 CPU 收到了 ack 消息后,再从 store buffer 中将数据写入到 local cache 中。有了 store buffer 之后性能提高了许多,但常言道:“有一利必有一弊。”store buffer 虽然提高了性能但是却引入了新的问题。


a = 0 , b = 0;
a = 1;
b = a + 1;
assert(b == 2);

假设变量 a 在 CPU 1 的 cache line 中 , 变量 b 在 CPU 0 的 cache line 中。上述代码的执行序列如下:

1. CPU 0 执行 a = 1。

2. CPU 0 local cache 中没有 a ,发生 cache miss 。

3. CPU 0 发送 read invalidate 消息获取 a ,同时让其他 CPU local cache 中的 a 被清除。

4. CPU 0 把需要写入 a 的值 1 放入了 store buffer 。

5. CPU 1 收到了 read invalidate 消息,回应了 read response 和 acknowledge 消息,把自己 local cache 中的 a 清除了。

6. CPU 0 执行 b = a + 1 。

7. CPU 0 收到了 read response 消息得到了 a 的值是 0 。

8. CPU 0 从 cache line 中读取了 a 值为 0 。

9. CPU 0 执行 a + 1 , 并写入 b ,b 被 CPU 0 独占所以直接写入 cache line , 这时候 b 的值为 1。

10. CPU 0 将 store buffer 中 a 的值写入到 cache line , a 变为 1。

11. CPU 0 执行 assert(b == 2) , 程序报错。

导致这个问题是因为 CPU 对内存进行操作的时候,顺序和程序代码指令顺序不一致。在写操作执行之前就先执行了读操作。另一个原因是在同一个 CPU 中同一个数据存在不一致的情况 , 在 store buffer 中是最新的数据, 在 cache line 中是旧的数据。为了解决在同一个 CPU 的 store buffer 和 cache 之间数据不一致的问题,引入了 Store Forwarding。store forwarding 就是当 CPU 执行读操作时,会从 store buffer 和 cache 中读取数据, 如果 store buffer 中有数据会使用 store buffer 中的数据,这样就解决了同一个 CPU 中数据不一致的问题。但是由于 Memory Ordering 引起的问题还没有解决。


内存操作顺序

Memory Ordering


a = 0 , b = 0;
void fun1() {   
  a = 1;   
  b = 1;
}

void fun2() {  
  while (b == 0) continue;  
  assert(a == 1);
}

​​​​​​​

假设 CPU 0 执行 fun1() , CPU 1 执行 fun2() , a 变量在 CPU 1 cache 中 , b 变量在 CPU 0 cache 中。 上述代码的执行序列如下:

  1.  CPU 0执行a=1的赋值操作,由于a不在local cache中,因此,CPU 0将a值放到store buffer中之后,发送了read invalidate命令到总线上去。
  2. CPU 1执行 while (b == 0) 循环,由于b不在CPU 1的cache中,因此,CPU发送一个read message到总线上,看看是否可以从其他cpu的local cache中或者memory中获取数据。
  3.  CPU 0继续执行b=1的赋值语句,由于b就在自己的local cache中(cacheline处于modified状态或者exclusive状态),因此CPU0可以直接操作将新的值1写入cache line。
  4. CPU 0收到了read message,将最新的b值”1“回送给CPU 1,同时将b cacheline的状态设定为shared。
  5. CPU 1收到了来自CPU 0的read response消息,将b变量的最新值”1“值写入自己的cacheline,状态修改为shared。
  6. 由于b值等于1了,因此CPU 1跳出while (b == 0)的循环,继续执行。
  7.  CPU 1执行assert(a == 1),这时候CPU 1的local cache中还是旧的a值,因此assert(a == 1)失败。
  8. CPU 1收到了来自CPU 0的read invalidate消息,以a变量的值进行回应,同时清空自己的cacheline。
  9. CPU 0收到了read response和invalidate ack的消息之后,将store buffer中的a的最新值”1“数据写入cacheline。

产生问题的原因是 CPU 0 对 a 的写操作还没有执行完,但是 CPU 1 对 a 的读操作已经执行了。毕竟CPU并不知道哪些变量有相关性,这些变量是如何相关的。不过CPU设计者可以间接提供一些工具让软件工程师来控制这些相关性。这些工具就是 memory barrier 指令。要想程序正常运行,必须增加一些 memory barrier 的操作。


写内存屏障

Store Memory Barrier

a = 0 , b = 0;
void fun1() {   
  a = 1;   
  smp_mb();   
  b = 1;
}

void fun2() {   
  while (b == 0) continue;
  assert(a == 1);
}

smp_mb() 这个内存屏障的操作会在执行后续的store操作之前,首先flush store buffer(也就是将之前的值写入到cacheline中)。smp_mb() 操作主要是为了让数据在local cache中的操作顺序是符合program order的顺序的,为了达到这个目标有两种方法:方法一就是让CPU stall,直到完成了清空了store buffer(也就是把store buffer中的数据写入cacheline了)。方法二是让CPU可以继续运行,不过需要在store buffer中做些文章,也就是要记录store buffer中数据的顺序,在将store buffer的数据更新到cacheline的操作中,严格按照顺序执行,即便是后来的store buffer数据对应的cacheline已经ready,也不能执行操作,要等前面的store buffer值写到cacheline之后才操作。增加smp_mb() 之后,操作顺序如下:

1. CPU 0执行a=1的赋值操作,由于a不在local cache中,因此,CPU 0将a值放 store buffer中之后,发送了read invalidate命令到总线上去。

2. CPU 1执行 while (b == 0) 循环,由于b不在CPU 1的cache中,因此,CPU发送一个read message到总线上,看看是否可以从其他cpu的local cache中或者memory中获取数据。

3. CPU 0执行smp_mb()函数,给目前store buffer中的所有项做一个标记(后面我们称之marked entries)。当然,针对我们这个例子,store buffer中只有一个marked entry就是“a=1”。

4. CPU 0继续执行b=1的赋值语句,虽然b就在自己的local cache中(cacheline处于modified状态或者exclusive状态),不过在store buffer中有marked entry,因此CPU0并没有直接操作将新的值1写入cache line,取而代之是b的新值”1“被写入store buffer,当然是unmarked状态。

5. CPU 0收到了read message,将b值”0“(新值”1“还在store buffer中)回送给CPU 1,同时将b cacheline的状态设定为shared。

6.  CPU 1收到了来自CPU 0的read response消息,将b变量的值(”0“)写入自己的cacheline,状态修改为shared。

7. 完成了bus transaction之后,CPU 1可以load b到寄存器中了(local cacheline中已经有b值了),当然,这时候b仍然等于0,因此循环不断的loop。虽然b值在CPU 0上已经赋值等于1,但是那个新值被安全的隐藏在CPU 0的store buffer中。

8. CPU 1收到了来自CPU 0的read invalidate消息,以a变量的值进行回应,同时清空自己的cacheline。

9. CPU 0将store buffer中的a值写入cacheline,并且将cacheline状态修改为modified状态。

10. 由于store buffer只有一项marked entry(对应a=1),因此,完成step 9之后,store buffer的b也可以进入cacheline了。不过需要注意的是,当前b对应的cacheline的状态是shared。

11.  CPU 0发送invalidate消息,请求b数据的独占权。

12.  CPU 1收到invalidate消息,清空自己的b cacheline,并回送acknowledgement给CPU 0。

13. CPU 1继续执行while (b == 0),由于b不在自己的local cache中,因此 CPU 1发送read消息,请求获取b的数据。

14. CPU 0收到acknowledgement消息,将b对应的cacheline修改成exclusive状态,这时候,CPU 0终于可以将b的新值1写入cacheline。

15. CPU 0收到read消息,将b的新值1回送给CPU 1,同时将其local cache中b对应的cacheline状态修改为shared。

16.  CPU 1获取来自CPU 0的b的新值,将其放入cacheline中。

17. 由于b值等于1了,因此CPU 1跳出while (b == 0)的循环,继续执行。

18. CPU 1执行assert(a == 1),不过这时候a值没有在自己的cacheline中,因此需要通过cache一致性协议从CPU 0那里获得,这时候获取的是a的最新值,也就是1值,因此assert成功。

通过上面的描述,我们可以看到,一个直观上很简单的给a变量赋值的操作,都需要那么长的执行过程,而且每一步都需要芯片参与,最终完成整个复杂的赋值操作过程。

上述这个例子展示了 write memory barrier , 简单来说在屏障之后的写操作必须等待屏障之前的写操作完成才可以执行,读操作则不受该屏障的影响。


顺序写操作导致了 CPU 的停顿

Store Sequences Result in Unnecessary Stalls

按照矛盾的角度来看解决了一个问题之后伴随着又产生了一个新的问题:每个cpu的store buffer不能实现的太大,其entry的数目不会太多。当cpu以中等的频率执行store操作的时候(假设所有的store操作导致了cache miss),store buffer会很快的被填满。在这种状况下,CPU只能又进入等待状态,直到cache line完成invalidate和ack的交互之后,可以将store buffer的entry写入cacheline,从而为新的store让出空间之后,CPU才可以继续执行。这种状况也可能发生在调用了memory barrier指令之后,因为一旦store buffer中的某个entry被标记了,那么随后的store都必须等待invalidate完成,因此不管是否cache miss,这些store都必须进入store buffer。为了解决这个问题引入了 invalidate queues 可以缓解这个状况。store buffer之所以很容易被填充满,主要是其他CPU回应invalidate acknowledge比较慢,如果能够加快这个过程,让store buffer尽快进入cacheline,那么也就不会那么容易填满了。

invalidate acknowledge不能尽快回复的主要原因是invalidate cacheline的操作没有那么快完成,特别是cache比较繁忙的时候,这时,CPU往往进行密集的loading和storing的操作,而来自其他CPU的,对本CPU local cacheline的操作需要和本CPU的密集的cache操作进行竞争,只要完成了invalidate操作之后,本CPU才会发生invalidate acknowledge。此外,如果短时间内收到大量的invalidate消息,CPU有可能跟不上处理,从而导致其他CPU不断的等待。

然而,CPU其实不需要完成invalidate操作就可以回送acknowledge消息,这样,就不会阻止发生invalidate请求的那个CPU进入无聊的等待状态。CPU可以buffer这些invalidate message(放入Invalidate Queues),然后直接回应acknowledge,表示自己已经收到请求,随后会慢慢处理。当然,再慢也要有一个度,例如对a变量cacheline的invalidate处理必须在该CPU发送任何关于a变量对应cacheline的操作到bus之前完成。

有了Invalidate Queue的CPU,在收到invalidate消息的时候首先把它放入Invalidate Queue,同时立刻回送acknowledge 消息,无需等到该cacheline被真正invalidate之后再回应。当然,如果本CPU想要针对某个cacheline向总线发送invalidate消息的时候,那么CPU必须首先去Invalidate Queue中看看是否有相关的cacheline,如果有,那么不能立刻发送,需要等到Invalidate Queue中的cacheline被处理完之后再发送。一旦将一个invalidate(例如针对变量a的cacheline)消息放入CPU的Invalidate Queue,实际上该CPU就等于作出这样的承诺:在处理完该invalidate消息之前,不会发送任何相关(即针对变量a的cacheline)的MESI协议消息。


读内存屏障

Load Memory Barrier

a = 0 , b = 0;
void fun1() {    
  a = 1;    
  smp_mb();    
  b = 1;
}

void fun2() {    
   while (b == 0) continue;
   assert(a == 1);
}

假设 a 存在于 CPU 0 和 CPU 1 的 local cache 中,b 存在于 CPU 0 中。CPU 0 执行 fun1() , CPU 1 执行 fun2() 。操作序列如下:

  1.  CPU 0执行a=1的赋值操作,由于a在CPU 0 local cache中的cacheline处于shared状态,因此,CPU 0将a的新值“1”放入store buffer,并且发送了invalidate消息去清空CPU 1对应的cacheline。

   2. CPU 1执行while (b == 0)的循环操作,但是b没有在local cache,因此发送read消息试图获取该值。

    3. CPU 1收到了CPU 0的invalidate消息,放入Invalidate Queue,并立刻回送Ack。

    4. CPU 0收到了CPU 1的invalidate ACK之后,即可以越过程序设定内存屏障(第四行代码的smp_mb() ),这样a的新值从store buffer进入cacheline,状态变成Modified。

    5. CPU 0 越过memory barrier后继续执行b=1的赋值操作,由于b值在CPU 0的local cache中,因此store操作完成并进入cache line。

    6. CPU 0收到了read消息后将b的最新值“1”回送给CPU 1,并修正该cacheline为shared状态。

    7.  CPU 1收到read response,将b的最新值“1”加载到local cacheline。

    8. 对于CPU 1而言,b已经等于1了,因此跳出while (b == 0)的循环,继续执行后续代码。

     9.  CPU 1执行assert(a == 1),但是由于这时候CPU 1 cache的a值仍然是旧值0,因此assert 失败。

     10. Invalidate Queue中针对a cacheline的invalidate消息最终会被CPU 1执行,将a设定为无效。

很明显,在上面场景中,加速 ack 导致fun1()中的memory barrier失效了,因此,这时候对 ack 已经没有意义了,毕竟程序逻辑都错了。怎么办?其实我们可以让memory barrier指令和Invalidate Queue进行交互来保证确定的memory order。具体做法是这样的:当CPU执行memory barrier指令的时候,对当前Invalidate Queue中的所有的entry进行标注,这些被标注的项次被称为marked entries,而随后CPU执行的任何的load操作都需要等到Invalidate Queue中所有marked entries完成对cacheline的操作之后才能进行。因此,要想保证程序逻辑正确,我们需要给 fun2() 增加内存屏障的操作,具体如下:​​​​​​​

a = 0 , b = 0;
void fun1() {    
  a = 1;    
  smp_mb();    
  b = 1;
}

void fun2() {    
  while (b == 0) continue;    
  smp_rmb();
  assert(a == 1);
 }

当 CPU 1 执行完 while(b == 0) continue;  之后, 它必须等待 Invalidate Queues 中的 Invalidate 变量 a 的消息被处理完,将 a 从 CPU 1 local cache 中清除掉。然后才能执行 assert(a == 1)。CPU 1 在读取 a 时发生 cache miss ,然后发送一个 read 消息读取 a ,CPU 0 会回应一个 read response 将 a 的值发送给 CPU 1。


许多CPU architecture提供了弱一点的memory barrier指令只mark其中之一。如果只mark invalidate queue,那么这种memory barrier被称为read memory barrier。相应的,write memory barrier只mark store buffer。一个全功能的memory barrier会同时mark store buffer和invalidate queue。

我们一起来看看读写内存屏障的执行效果:对于read memory barrier指令,它只是约束执行CPU上的load操作的顺序,具体的效果就是CPU一定是完成read memory barrier之前的load操作之后,才开始执行read memory barrier之后的load操作。read memory barrier指令象一道栅栏,严格区分了之前和之后的load操作。同样的,write memory barrier指令,它只是约束执行CPU上的store操作的顺序,具体的效果就是CPU一定是完成write memory barrier之前的store操作之后,才开始执行write memory barrier之后的store操作。全功能的memory barrier会同时约束load和store操作,当然只是对执行memory barrier的CPU有效。

看完本文有收获?请分享给更多人

微信关注「黑帽子技术」加星标,看精选 IT 技术文章

查看原文

赞 0 收藏 0 评论 0

黑帽子技术 发布了文章 · 2020-04-05

带你了解缓存一致性协议 MESI

1 CPU Cache 结构

CPU 在执行指令的时候需要从 memory 中获取指令和需要的数据,但是 CPU 的速度要比 memory 快很多,这就导致了 CPU 大部分时间都不是在做运算上而是用在了和 memory 进行数据的 I/O 过程中,这样性能是很低的。这就导致了 CPU cache 的产生,CPU 将数据从 memory 读取到 cache 中,在 cache 中对数据进行读写的速度是很快的,这样就提高了性能。CPU 执行运算时不可能需要某一个数据就去读取一次,这样就增加了 I/O 的频率,导致性能低下。所以会一次性读取一块内存的数据存放到 cache 中, 这个块称为 “cache line” , cache line 的大小是固定的, 通常是 2 的 N 次方,在 16 ~ 256 byte 不等。当某个数据首次被 CPU 访问时, cache 中不存在,这称为 “cache miss” (或 “startup” 或 “warmup” cache miss)。这意味着 CPU 必须要去 memory 中读取该数据,这时候 CPU 必须等待(stalled)。当 cache 装满后,后续的 cache miss 需要置换 cache 中现有的数据,这样的 cache miss 被称为 “capacity miss” 。如果随后又访问了被替换的 cache line ,这时的 cache miss 被称为 “associativity miss” 。 

当 CPU 写数据的时候,需要保证该数据在多个 CPU cache 之间的一致性。在写之前必须先让其他 CPU cache 中的该数据失效,之后才可以安全的写数据。如果这个数据已经存在于要执行写指令的 CPU cache 中,但是是 read only 的,这个过程被称为 "write miss" ,一旦其他 CPU cache 中的该数据都失效后,该 CPU 可以不断的写或读其 cache 中的数据。如果另外某一个 CPU 尝试访问该数据,会形成一次 cache miss ,这时称为 “communication miss” 。因为这通常是多个 CPU 之间使用缓存通信造成的。

2. 缓存一致性协议

缓存一致性协议用于管理多个 CPU cache 之间数据的一致性,这些协议十分复杂,在这里我们仅讨论 MESI 协议的四种状态。

M : modified

E : exclusive

S : shared

I : invalid

协议在每一个 cache line 中维护一个两位的状态 “tag” ,这个 “tag” 在 cache line 的物理地址或者数据后。

modified 状态的 cache line 是由于相应的 CPU 进行了写操作,同时也表示该数据不会存在于其他 CPU 的 cache 中。也就说明这个最新的数据目前是被执行写操作的 CPU cache line 独占的。因为如此当前 cache 需要对该数据负责,比如将该数据传递给其他 CPU cache ,或者是将该数据写回到 memory 中。而这些操作都需要在 reuse cache line (置换)之前完成。

exclusive 状态和 modified 状态类似,区别是 CPU 还没有修改 cache line 中的数据。在 exclusive 状态下 CPU 可以不通知其他 CPU 而直接 cache line 进行操作。也就是说 exclusive状态是该 CPU 对这个数据的独占。此时由于 memory 中和 cache line 中的数据都是最新的,所以不需要对 exclusive 状态的 cache line 执行写回 memory 的操作就可以直接 reuse。

shared 状态的 cache line ,其数据可能在一个或者是多个 CPU cache 中,此时 CPU 不能直接修改 cache line 中的数据,而是需要先通知其他 CPU cache 。和 exclusive 一样, shared 状态的 cache line 对应的 memory 中的数据也是最新的,因此也可以直接 reuse cache line。

invalid 状态时 cache line 是空的。当新的数据要进入 cache 的时候优先选择 invalid 的 cache line ,之所以如此是因为如果选中其他状态的 cache line 会涉及到 cache line 中数据的置换 , 而之后如果这个被替换的数据被访问到会造成 cache miss ,这样造成很大的开销。

 image.png

3 MESI 协议消息

  1. Read。"read" 消息用来获取指定物理地址上的 cache line 数据。
  2. Read Response。该消息携带了 “read” 消息所请求的数据。read response 可能来自于 memory 或者是其他 CPU cache。
  3. Invalidate。该消息将其他 CPU cache 中指定的数据设置为失效。该消息携带物理地址,其他 CPU cache 在收到该消息后,必须进行匹配,发现在自己的 cache line 中有该地址的数据,那么就将其从 cahe line 中移除,并响应 Invalidate Acknowledge 回应。
  4. Invalidate Acknowledge。该消息用做回应 Invalidate 消息。
  5. Read Invalidate。该消息中带有物理地址,用来说明想要读取哪一个 cache line 中的数据。这个消息还有 Invalidate 消息的效果。其实该消息是 read + Invalidate 消息的组合,发送该消息后 cache 期望收到一个 read response 消息。
  6. Writeback。 该消息带有地址和数据,该消息用在 modified 状态的 cache line 被置换时发出,用来将最新的数据写回 memory 或其他下一级 cache 中。

4 MESI状态图

根据 MESI 协议消息的发送和接收或者是对数据的读写,cache line 的状态会在 modified , exclusive , shared , invalid 之间进行转换。

  1. cache 通过 writeback 将数据回写到 memory 或者下一级 cache 中。这时候状态由 modified 变成了 exclusive 。
  2. cpu 直接将数据写入 cache line ,导致状态变为了 modified 。
  3. CPU 收到一个 read invalidate 消息,此时 CPU 必须将对应 cache line 设置成 invalid 状态 , 并且响应一个 read response 消息和 invalidate acknowledge 消息。
  4. CPU 需要执行一个原子的 readmodify-write 操作,并且其 cache 中没有缓存数据。这时候 CPU 就会在总线上发送一个 read invalidate 消息来请求数据,并试图独占该数据。CPU 可以通过收到的 read response 消息获取到数据,并等待所有的 invalidate acknowledge 消息,然后将状态设置为 modifie 。
  5. CPU需要执行一个原子的readmodify-write操作,并且其local cache中有read only的缓存数据(cacheline处于shared状态),这时候,CPU就会在总线上发送一个invalidate请求其他cpu清空自己的local copy,以便完成其独自霸占对该数据的所有权的梦想。同样的,该cpu必须收集所有其他cpu发来的invalidate acknowledge之后才能更改状态为 modified。
  6. 在本cpu独自享受独占数据的时候,其他的cpu发起read请求,希望获取数据,这时候,本cpu必须以其local cacheline的数据回应,并以read response回应之前总线上的read请求。这时候,本cpu失去了独占权,该cacheline状态从Modified状态变成shared状态(有可能也会进行写回的动作)。
  7. 这个迁移和f类似,只不过开始cacheline的状态是exclusive,cacheline和memory的数据都是最新的,不存在写回的问题。总线上的操作也是在收到read请求之后,以read response回应。
  8. 如果cpu认为自己很快就会启动对处于shared状态的cacheline进行write操作,因此想提前先霸占上该数据。因此,该cpu会发送invalidate敦促其他cpu清空自己的local copy,当收到全部其他cpu的invalidate acknowledge之后,transaction完成,本cpu上对应的cacheline从shared状态切换exclusive状态。还有另外一种方法也可以完成这个状态切换:当所有其他的cpu对其local copy的cacheline进行写回操作,同时将cacheline中的数据设为无效(主要是为了为新的数据腾些地方),这时候,本cpu坐享其成,直接获得了对该数据的独占权。
  9. 其他的CPU进行一个原子的read-modify-write操作,但是,数据在本cpu的cacheline中,因此,其他的那个CPU会发送read invalidate,请求对该数据以及独占权。本cpu回送read response”和“invalidate acknowledge”,一方面把数据转移到其他cpu的cache中,另外一方面,清空自己的cacheline。
  10. cpu想要进行write的操作但是数据不在local cache中,因此,该cpu首先发送了read invalidate启动了一次总线transaction。在收到read response回应拿到数据,并且收集所有其他cpu发来的invalidate acknowledge之后(确保其他cpu没有local copy),完成整个bus transaction。当write操作完成之后,该cacheline的状态会从Exclusive状态迁移到Modified状态。
  11. 本CPU执行读操作,发现local cache没有数据,因此通过read发起一次bus transaction,来自其他的cpu local cache或者memory会通过read response回应,从而将该 cache line 从Invalid状态迁移到shared状态。
  12. 当cache line处于shared状态的时候,说明在多个cpu的local cache中存在副本,因此,这些cacheline中的数据都是read only的,一旦其中一个cpu想要执行数据写入的动作,必须先通过invalidate获取该数据的独占权,而其他的CPU会以invalidate acknowledge回应,清空数据并将其cacheline从shared状态修改成invalid状态。

查看原文

赞 0 收藏 0 评论 0

黑帽子技术 关注了专栏 · 2020-04-05

终身学习者

我要先坚持分享20年,大家来一起见证吧。

关注 52028

黑帽子技术 关注了专栏 · 2020-04-05

kubernetes solutions

专注k8s,serverless,service mesh,devops

关注 2236

黑帽子技术 关注了专栏 · 2020-04-05

民工哥技术之路

公众号:民工哥技术之路、《Linux系统运维指南 从入门到企业实战》作者。专注系统架构、高可用、高性能、高并发,数据库、大数据、数据分析、Python技术、集群中间件、后端等开源技术分享。

关注 27503

黑帽子技术 关注了专栏 · 2020-04-05

滴滴网约车前端团队

滴滴出行网约车前端团队

关注 918

黑帽子技术 关注了专栏 · 2020-04-05

JowayYoung谈前端

谢谢曾经努力的自己,欢迎关注公众号『 IQ前端 』,一个专注于CSS/JS开发技巧的前端公众号,更多前端小干货等着你喔

关注 4356

黑帽子技术 关注了专栏 · 2020-04-05

猪哥python

微信搜索:裸睡的猪

关注 2667

黑帽子技术 关注了用户 · 2020-04-05

谭光志 @woai3c

公众号:前端编程技术分享

知乎:https://www.zhihu.com/people/...

github: https://github.com/woai3c

关注 10285

黑帽子技术 关注了用户 · 2020-04-05

Java技术栈 @javastack

Java技术栈以 Java 后端技术为主,包括 Java 核心技术、多线程编程、Spring Boot、Spring Cloud、缓存、消息队列、架构设计等各种技术干货、Java 面试题、各种学习资料等。微信公众号:Java技术栈(id:javastack),搜索关注吧!

关注 9396

认证与成就

  • 获得 0 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-04-05
个人主页被 307 人浏览