请教大家关于多核并发编程中, cache 一致性的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
RedBlackTree
V2EX    程序员

请教大家关于多核并发编程中, cache 一致性的问题

  RedBlackTree 2021-03-26 15:48:50 +08:00 4523 次点击
这是一个创建于 1672 天前的主题,其中的信息可能已经有所发展或是发生改变。

假设两个线程 A 、B,共享变量 V,A 和 B 分别跑在 CPU1 、CPU2 上,各自 cache 中有 V 所在 cache line 的副本,其中的 V 分别为 V1 、V2 。

那么当 A 直接修改 V,而没有使用锁、CAS 等同步原语,那么 CPU1 只是单纯修改自己的 V1 而没有写回内存,会不会导致 CPU2 的 V2 失效?即一个 CPU 的某块缓存失效的时机是内存发生了修改,还是其他 CPU 的相同缓存发生了修改?

我的理解是因为 cache 的修改写入内存是 write back,而不是立即写入内存,所以没有使用同步原语的情况下,线程 A 对 V1 的修改,应该对内存和线程 B 都是不可见的,直到未来某一时刻 A 的 cache 刷回内存,B 所在的 CPU2 中的 cache line 才会被标记为失效,之后才能读到 A 对 V 的修改。

因此为了保证多个线程对共享变量并发操作的可见性,访问共享变量需要同步原语,保证每次写都写到内存上,每次读都读到是内存上最新的值。

这是我从 The Go Memory Model 和 TGPL 书上的理解,不知道理解得对不对,顺便问下大家有没有系统讲解多核缓存的书籍或资料?

24 条回复    2021-03-27 17:07:00 +08:00
RedBlackTree
    1
RedBlackTree  
OP
   2021-03-26 16:10:34 +08:00
因为读 Ardanlabs 的 Go Scheduler 这篇文章( https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html )时,讲到 cache 时这么写道:
If one Thread on a given core makes a change to its copy of the cache line, then through the magic of hardware, all other copies of the same cache line have to be marked dirty. When a Thread attempts read or write access to a dirty cache line, main memory access (~100 to ~300 clock cycles) is required to get a new copy of the cache line.
修改 cache line 就会导致其他 CPU 的 cache 失效,但我觉得不是 cache 修改写回内存时才会这样吗?不然为什么要使用同步原语同步内存呢?有了困惑。
kkbblzq
    2
kkbblzq  
   2021-03-26 16:13:40 +08:00
你问的是 MESI ?
kuko126
    3
kuko126  
   2021-03-26 16:25:08 +08:00
https://www.bilibili.com/video/BV1X54y1Q75J
这个视频是讲的 java 的,里面的缓存失效部分应该是一致的,不知道能不能解答你的疑问
yufpga
    4
yufpga  
   2021-03-26 16:32:18 +08:00
可以看看 MESI. cache 一致性协议应该是硬件(CPU)实现的, 但事实上硬件要做到强一致性同时兼顾性能也很难,所以实现上会弱化为最终一致性, 一般情况下也没有太大问题, 但是在并发多核的情况下,在某些临界点上还是会出现数据不一致的情况. 所以 CPU 设计的时候提供给程序或者编译器内存屏障机制, 来处理这种情况
lewis89
    5
lewis89  
   2021-03-26 17:04:05 +08:00
看下 TSO 宽松内存模型,多核并发的时候,肯定要用机器提供的 mfence lock 等指令来进行强制同步
LinSP/td>
    6
LinSP  
   2021-03-26 17:24:15 +08:00
可以看下读写屏障?。
LeeReamond
    7
LeeReamond  
   2021-03-26 17:27:03 +08:00
有意思的讨论,关注一波。我理解与 LZ 有一个不同,即如果没有使用任何同步,线程 B 在忙时,即使 A 中的缓存被刷回内存,B 的缓存一样不会被标记为失效。意思是如果 B 使用的是 while 循环的话那么会永远运行下去
Brentwans
    8
Brentwans  
   2021-03-26 17:29:06 +08:00
没看文章基于你的疑问和理解。
这些行为基于内存模型的,其模型就是这样。go 的内存模型不太了解,但是比如 java 的 volatile 关键字的语义就是该变量,读取会放弃 cache 中,从内存中刷数据。写会强制刷到内存。至于底层真实情况如何,肯定不同硬件和系统有不同实现。只要在 jave 这一层,表现的是其定义这样就可以了。
“修改 cache line 就会导致其他 CPU 的 cache 失效,但我觉得不是 cache 修改写回内存时才会这样吗?不然为什么要使用同步原语同步内存呢?有了困惑” 这个具体实现有关系啊,“修改 cache line 导致与之相关的 line 失效”就是这样实现的啊,
而你说的“cache line 写回才会导致相关失效” 那是你的理解吧。要我站在 java 内存模型,我会也会觉得不思议,为什么一个 core 的 cache line 写回,其他 core 的就要失效呢?没关系,就是这样,你创建的模型就是这样,我如果我用你这个模型,按照你制定的模型的行为使用即可。
dalabenba
    9
dalabenba  
   2021-03-26 17:33:07 +08:00 via Android   1
A 写了 v 不加同步原语对 B 不可见是真的,不过不是因为 cache,而是因为可能有其他的 write buffer, cache 一致性是由硬件保证的,不需要同步原语
liuminghao233
    10
liuminghao233  
   2021-03-26 17:48:09 +08:00 via iPhone
arm 的不清楚
x86 写是有 buffer 的 这个不是 l123 这种 buffer
你需要 mfence 来清掉 store buffer 和 write combining buffer,然后 mesi 会让这个写对全部 u 可见
runliuv
    11
runliuv  
   2021-03-26 18:00:24 +08:00
看你的描述,和 CPU 没半毛钱关系。
“假设两个线程 A 、B,共享变量 V”,一看就是工作在应用层(进程)。OS 把 CPU 封装成统一的 CPU 资源给应用。应用层一般没办法直接操作到 CPU CACHE 的。
NUT
    12
NUT  
   2021-03-26 19:26:21 +08:00
我乱说的,
感觉楼主说的有些类似于 JMM 的规则。
java volatile 是保证属性可见性,线程栈改完就刷到主内存,读也是从主内存拿,其实靠的禁止 cpu 指令重排。
但是 volatile 并不是保证 cas 操作。
cas 是保证替换的时候是期望的条件, 当然会出现 aba 的问题。但也有方式解决,加标记呗。
secondwtq
    13
secondwtq  
   2021-03-26 19:33:19 +08:00   1
缓存一致性( Cache Coherence )保证的就是不同 cache 的视图是一样的,所以只要是 cache coherent 的系统,当一个 cache 中的数据发生变化时,其他 cache 中对应的数据也会发生变化。
楼主描述的“线程 A 对 V1 的修改,应该对内存和线程 B 都是不可见的,直到未来某一时刻 A 的 cache 刷回内存,B 所在的 CPU2 中的 cache line 才会被标记为失效,之后才能读到 A 对 V 的修改”不符合缓存一致性的定义。

但是完全实现这一保证,性能会出问题处理器在执行 store 操作之前,需要告诉所有其他处理器这个 cacheline 被 invalidate 了,然后还需要等其他处理器回复,才能完成 store 操作。但是大多数情况下我并不需要等这个 store 。所以就出现了 store buffer 缓存 store 操作,但是为了保证本处理器的程序顺序正常,store buffer 必须配合 load-to-store forwarding,然后就出现了本处理器和其他处理器所感知到的程序顺序不一样的问题,以及可见性的问题。
再往后去看 Memory Barriers: a Hardware View for Software Hackers

当然好像在各路 Java 教材中,“懒得刷进主存的 ‘cache’”事实上替 store buffer 之类的优化背了锅。
secondwtq
    14
secondwtq  
   2021-03-26 19:42:11 +08:00
@secondwtq s/load-to-store forwarding/store-to-load forwarding
还有忘了说 ... 因为 store buffer 的存在,一般的 store 操作在不填满 store buffer 的前提下的开销是很小的,和 load 差远了。

“访问共享变量需要同步原语”只是因为内存模型不保证未保护的访问,这是概念层面而非实现层面的问题。当然内存模型是综合考虑软件需求和硬件实现才提出的,所以折腾实现也是有用的。

据说 ARM 已经在架构层面加入了目前最流行的 sequential consistency 模型,不知道效果如何。
Jooooooooo
    15
Jooooooooo  
   2021-03-26 20:46:22 +08:00
差不多是你说的那样, 可以搜下 MESI
Orlion
    16
Orlion  
   2021-03-26 21:13:37 +08:00 via Android   1
4 楼正解。正好前段时间我也深入研究了这个问题,并且整理成了一篇博客: https://blog.fanscore.cn/p/34/ 楼主可以参考下,或许对你有帮助
sfqtsh
    17
sfqtsh  
   2021-03-26 21:22:31 +08:00 via Android
看 IBM 的 PDF:
Memory Barriers: a Hardware View for Software Hackers
byaiu
    18
byaiu  
   2021-03-27 00:33:53 +08:00 via iPhone
同步原语应该是保证读写的操作能推到 mesi 的接口上去,之后就由硬件来保证一致性了。

推而 memory order 是到 mdsi 之前的考量。
xcstream
    19
xcstream &nsp;
   2021-03-27 01:58:15 +08:00
“不要通过共享内存来通信,而应该通过通信来共享内存 “
surbomfla
    20
surbomfla  
   2021-03-27 07:38:24 +08:00
@xcstream 老 gopher 了
GrayXu
    21
GrayXu  
   2021-03-27 12:29:20 +08:00
感觉上面大家讲的缓存一致性实现好像是不同 level 的…在 CPU 的角度来说,MESI 实现就是这样的。
wangliran1121
    22
wangliran1121  
   2021-03-27 16:36:59 +08:00
《 Memory Barriers: a Hardware View for Software Hackers 》
wangliran1121
    23
wangliran1121  
   2021-03-27 16:39:52 +08:00
讨论这个问题不能抛开 CPU 架构
Dganzh
    24
Dganzh  
   2021-03-27 17:07:00 +08:00
刚看毛剑的课程,讲到 The Go Memory Model,然后就刷到这里,这是巧合?
关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1111 人在线   最高记录 6679       Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 25ms UTC 23:16 PVG 07:16 LAX 16:16 JFK 19:16
Do have faith in what you're doing.
ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86