内存屏障产生的背景–乱序
由于现代CPU的运行速度往往要比内存要快得多——一般在从内存获取一个变量的同时,CPU可以执行数百条指令,因此现代计算机架构往往在CPU和内存之间增加一级缓存,以允许在缓存中快速访问较为频繁使用的数据。与此同时,CPU被设计成在从内存中获取数据的同时,可以执行其他指令和内存引用,这就导致了指令和内存引用的乱序执行。为了解决这一内存乱序问题,引入了各种同步原语,这些原语通过使用内存屏障来实现多处理器之间内存访问的顺序一致性。
仅仅在两个CPU之间存在需要通过共享内存来实现交互的可能时,才需要使用内存屏障。
如果有能力,请展开讲解缓存一致性协议。
乱序时代码的执行过程:
1将a赋值0
2将b赋值0
3将a赋值1,但是只是1写入到store buffer中,原因:假设该CPu有两个核,其中CPUo赋值1给a时,需要通知cPU1,且需要等到cPU1的通知后才能真正赋值,这是为了保证数据的一致性,由于发通知和等回复的时间太长,会影响CPu的性能,因此,CPUo会将1暂时放在store buffer中,然后直接执行b=a+1。
4执行b=a+1,这时候b的结果并没有按照预想的变成2,而是为1.
5将1从store buffer中赋值,这时候a才真正的被赋值为1
6执行assert时,系统报错
a和b被加载,此时a=o,b=0;
a被CPU1赋值为1,如此同时b被CPU2赋值为a+1,如果这个动作是同时发生,那么此时a=1,b=0+1,这和代码本来表达的目的不匹配;
这就是CPU级乱序,这种现象的发生有一定的概率;
正确的顺序应该为a被CPU1赋值为1,将数据同步到共享换存后,CPU2再执行b=a+1。
内存屏障指令
内存屏障有两个作用:
- 阻止屏障两侧的指令重排序;
- 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。·对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。
除了表格中的三种指令外,还有一种方式能够达到内存屏障的作用,那就是在指令前加LOCK前缀。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。它后面可以跟ADD,ADC,AND,BTC,BTR,BTS,CMPXCHG,CMPXCH8B, DEC,INC,NEG, NOT,OR,SBB, SUB,XOR,XADD, and XCHG等指令。
Lock前缀实现了类似的能力.
- 它先对总线/缓存加锁,然后执行后面的指令,最后释放锁后会把高速缓存中的脏数据全部刷新回主内存。
- 在Lock锁住总线的时候,其他CPU的读写请求都会被阻塞,直到锁释放。Lock后的写操作会让其他cPu相关的cache line失效,从而从新从内存加载最新的数据。这个是通过缓存一致性协议做的。