因MemoryBarrier引发的疑问:多线程数组传数据会读过时数据吗?
嘿,这个问题问到点子上了——很多人刚开始接触内存屏障(MemoryBarrier)的时候,都会卡在CPU缓存与主存交互、指令重排序这两个关键点上,我来给你拆解清楚。
首先得明确这类问题的典型场景(毕竟你没贴出具体代码,但这是线程间共享数据的常见模型):
- ThreadA 执行的逻辑大概是先更新数组元素,再设置一个标记位通知ThreadB:
data[index] = updatedValue; flag = true; - ThreadB 则是循环等待标记位,一旦看到标记位为true就读取数组元素:
while (!flag); int result = data[index];
在这种场景下,ThreadB完全有可能读取到过时数据——也就是ThreadA已经修改了data[index],但ThreadB拿到的却是修改前的旧值。原因主要有两个:
1. CPU缓存的一致性问题
现代CPU都有多级缓存(L1/L2/L3),每个CPU核心都有自己的私有缓存。当ThreadA所在的核心修改data[index]时,这个修改首先会写到自己的私有缓存里,并不会立刻同步到主存。而ThreadB所在的核心如果之前加载过data[index]到自己的缓存,它会优先从本地缓存读取数据,这时候就会拿到还没被更新的旧值——这就是你说的“过时数据”。
2. 指令重排序的坑
编译器或者CPU为了提升执行效率,会对没有数据依赖的指令进行重排序。比如ThreadA的两行代码,data[index] = updatedValue和flag = true之间没有直接的数据依赖,CPU或编译器可能会把它们的执行顺序调换:先设置flag = true,再更新data[index]。这时候ThreadB看到flag为true后立刻读取data[index],但此时ThreadA还没完成数组元素的修改,自然拿到的就是过时数据。
那MemoryBarrier是怎么解决这个问题的?
- 给ThreadA加写内存屏障:在
flag = true之前插入写屏障,会强制把之前所有写操作(比如修改data[index])的缓存更新同步到主存,同时阻止这些写操作被重排到屏障之后。 - 给ThreadB加读内存屏障:在读取
data[index]之前插入读屏障,会强制从主存重新加载data[index]的数据,同时阻止读操作被重排到屏障之前。
这样就能保证ThreadB看到flag为true时,data[index]的更新已经同步到主存,不会读到过时数据。
内容的提问来源于stack exchange,提问作者Vyacheslav Spirin

