操作系统的内存模型
所有的数据运算都需要通过CPU来执行,所有的数据都是存储在内存中的,但是CPU的速度非常快,而内存的速度相比于CPU来说非常慢,因此CPU不能直接去操作内存,所以CPU设置了一系列的高速缓存,就是我们常常看到的一级缓存(L1)、二级缓存(L2)、三级缓存(L3)等,如果需要CPU需要操作数据,则首先将数据从内存(主内存)中读取到高速缓存中来,然后再层层传递直到传递到寄存器
,CPU就可以通过指令集
对数据进行运算了
如果你的系统只有一个CPU,那么就不会有数据的线程安全问题,因为同时只会有一个线程来操作数据,再将数据写回主内存,但是现代计算机基本不会再有单CPU的机器了,所以要面对多线程的情况,比如两个线程同时对变量i进行++操作,按理来说结果应该是i = 3才对,但是由于高速缓存的存在,导致出现下面的结果
变量i经过两次操作之后的结果还是2,很明显,在CPU1对变量i进行修改的时候从内存中读取到的i值为1,然后缓存到自己的高速缓存中并执行i然后将结果再次缓存到高速缓存再写入到主内存,CPU2在同时
也做了同样的操作,所以导致了最后i++两次之后的结果错误,这种错误不是我们想看到的
CPU为了解决这种问题,有两个解决方案
-
总线锁
:在CPU操作数据时会锁定数据锁在的主内存块,在操作期间其他CPU都无法操作该块内存的数据,因此也就不会出现数据错误问题,但是这种总线锁效率比较低,所以CPU又有了一种新的解决方法,缓存锁 -
缓存锁
:缓存一致性协议(MESI),针对于上面这种情况,如果CPU将变量i的值写入到了主内存,那么它会通知其他CPU,该数据已经被修改过了,其他CPU的数据都已经失效了,需要重新从主内存中读取该数据- MESI:其实对应了缓存的四种状态:
- M,Modified,缓存有效,但是和内存中的数据不一致了
- E,Exclusive,互斥,缓存有效,内存和缓存的数据一致并且数据只存在于当前缓存中
- S,Shared,共享态,缓存有效,内存和缓存数据一致并且数据存储在多个缓存中
- I,Invalid,缓存无效
- MESI:其实对应了缓存的四种状态:
在上面的例子中。变量i就是共享变量,因此其在CPU中的缓存初始状态为S,接着CPU1对变量i进行写入,CPU1本地缓存状态转换路径为S -> E -> M,CPU2中变量i的缓存状态转换路径为 S -> I,也就是缓存失效,那么CPU2会再次从主内存中读取最新的变量i的值
并发的三大问题
- 原子性
- 有序性
- 可见性
原子性
原子性,即一个操作是不可被分割的,例如i这个操作,其实可以被分为3步操作,首先读取变量i的值,然后对其值加1,再将新值写入到i所在的内存中,那么i就不是一个原子操作
有序性
有时候为了提高代码的执行效率,CPU和编译器可能会对指令进行重排序,在不改变代码运行结果的前提下改变代码的执行顺序
可见性
多线程工作时,对共享变量的修改操作,比如CPU1修改了变量i的值然后写入主内存,在CPU1写入主内存之前CPU2就需要用到变量i了,于是CPU2从主内存中读取到了变量i的旧值,然后CPU1才将变量i的值写入到主内存,这就是可见性问题,多CPU环境下对共享变量的修改不能及时被其他CPU看到