线程安全性问题之硬件层面

一.CPU高速缓存

线程是CPU调度的最小单元,线程出现的目的是为了更高效的利用CPU的计算处理能力,但是大部分的计算任务并不是仅仅依靠计算机的“计算”就能完成,处理器在读取运算数据,存储运算结果的过程中,还需要与内存进行交互,这个IO操作几乎是不能消除的。由于计算机的存储设备与处理器的计算速度差距非常大,所以现代的计算机都会增加一层读写速度尽可能接近处理器计算速度的高速缓存来作为内存与处理器之间的缓冲,将计算需要使用的数据复制到高速缓存中,让计算任务能够快速进行,计算完成后,在将结果从缓存同步到内存中。
从任务管理器中可以看到自己的计算机CPU和缓存的信息:
这里写图片描述
高速缓存越接近CPU速度越快,容量越小。现在的计算机大部分都有二级或三级缓存,如图,L1,L2,L3,缓存又可分为指令缓存(用i表示,缓存程序的代码)和数据缓存(用d表示,缓存程序的数据)。L3缓存以前是服务器才会有的,现在家用电脑也有了,他出现的主要目的是进一步降低内存操作的延迟问题。

L1 Cache,一级缓存,本地 core 的缓存,分成 32K 的数据缓存 L1d 和 32k 指
令缓存 L1i,访问 L1 需要 3cycles,耗时大约 1ns; 
L2 Cache,二级缓存,本地 core 的缓存,被设计为 L1 缓存与共享的 L3 缓存
之间的缓冲,大小为 256K,访问 L2 需要 12cycles,耗时大约 3ns; 
L3 Cache,三级缓存,在同插槽的所有 core 共享 L3 缓存,分为多个 2M 的
段,访问 L3 需要 38cycles,耗时大约 12ns;

二.高速缓存带来的问题

高速缓存带来的问题即缓存一致性问题,在多核CPU机器中,当一个CPU读取主存的数据缓存到自己的高速缓存中时,另一个CPU也在做同一件事,并且把读取的值改变了,改变后把值同步到自己的高速缓存中,但是没有写入到主存,当第一个CPU去访问这个值的时候,由于第二个CPU没有把值同步到主存,所以,第一个CPU读取到的值还是改变之前的值,这时就会出现数据不一致的情况。
因为在多核CPU下会存在指令并行执行的情况,而各个CPU之间的数据又不共享就会导致缓存一致性问题,为了解决这个问题,各个CPU生产厂商提供了相应的解决方案。

三.解决方案

1.总线锁
当一个CPU对其缓存中的数据进行操作时,往总线中发送一个#Lock信号,其他的处理器请求将会被阻塞,该处理器可以独占共享内存。
特点:总线锁相当于把整个CPU和内存之间的通信锁住了,这种方式会导致CPU性能下降。所以出现了缓存锁。
2.缓存锁
如果缓存在处理器缓存行中的内存区域在#Lock期间被锁定(如果声明了CPU的锁机制,会生成一个#Lock指令),当他执行锁操作回写内存时,处理器不会在总线上声明#Lock信号,而是修改内部的缓存地址,通过缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时被两个以上处理器缓存的内存区域的数据去做修改操作,当另外一个处理器回写已经被锁定的缓存行的数据时,当前处理器缓存行的数据将会无效。
特点: 1.#Lock前缀指令(汇编指令)会引起处理器缓存回写到内存,在P6系列以后的处理器中,Lock指令一般不锁总线,只锁缓存。
2.一个处理器的缓存回写到内存会导致其他处理器的缓存无效。

四.缓存一致性机制

1.不同的操作系统或CPU架构,支持的协议不一样,每个处理器上会有一套完整的协议来保证缓存的一致性,如强一致性协议,MESI,比较经典的一种就是MESI协议了(它主要是在CPU缓存中保存一个标记位来实现 的),也是大部分处理器都在使用的。
M:modified,修改缓存,当前CPU的缓存数据已经被修改,和内存中的数据不一致了
E:exclusive,独占缓存,当前CPU的缓存和内存中的数据保持一致,而且其他处理器没有缓存该数据
S:shared,共享缓存,数据和内存中数据一致,并且该数据存在于多个CPU缓存中
I:invalid,失效缓存,CPU的缓存已经不能使用 了
2.嗅探协议:每个处理器的缓存控制器不仅知道自己的读写操作,同时 也在监听着其他缓存的读写操作。
3. CPU的读取会遵循几个原则:
a.如果缓存的状态是I,就从内存中读取
b.如果缓存处于M或者E的CPU嗅探到其他CPU有读的操作,就把自己的缓存同步到内存,并把自己的状态设置为S
c.只有缓存状态是M或者E的时候,CPU才可以修改缓存

五.CPU的优化执行

除了增加高速缓存以外,为了更充分利用处理器的运算单元,处理器可能会对输入的代码进行乱序执行优化,处理器会在计算之后将乱序执行的结果处理保证该结果与顺序执行之后的结果一致,但并不保证程序中各个语句先后的计算顺序和输入时的一致,这就是CPU的优化执行,与其类似的是编程语言的编译器的优化,比如指令重排序。Java中在保证不影响我们最终执行的代码语义的情况下,允许编译器和指令器对我们的代码进行优化和指令的重排序去提升CPU的利用率。

上一篇:java线程停止的几种方法

猜你喜欢

转载自blog.csdn.net/lx_Frolf/article/details/82560911
今日推荐