操作系统 锁机制和内存一致性

来源:互联网 发布:业务流程图制作软件 编辑:程序博客网 时间:2024/06/05 13:29

写在前面:

想要深刻了解Java中的锁机制 还是要了解下操作系统层面的锁机制以及CPU缓存和内存一致性等知识 所以用一篇博客来记录整理一下

早期CPU处理数据是先从内存中读取数据加载到比如寄存器中,处理完之后再刷新回主存。但是这里有一个问题就是内存的数据读取速度是比较慢的,而CPU的处理时间很快,造成了很多时候CPU都是在等待从内存中读入数据再处理,白白消耗了CPU资源,这个时候就引入的缓存的概念。

缓存是一种可以进行高速数据交换的存储器,因此我们可以先把内存中的一部分可能会被访问的数据预先调入缓存中,CPU每次要读取数据的时候,首先从缓存中查找,如果命中就进行处理,若没有才去内存中读取,如果命中率很高的话效率还是非常快的。

现在基本都是多核CPU,每个CPU都有一个缓存,每次大家都是从主存中读取数据到缓存中,然后如果修改了数据再刷新回去,但是可能会造成内存不一致性的原因。比如在内存中有一个变量i,它的值是0,CPU A和B的缓存中i都是0,且都对i进行了加1操作,此时CPU A先把数据刷新回主存,主存中的i为1,而CPU B并不知道内存中的值改变了,因此它也把自己的值刷新回主存 这样就导致主存中的变量i的值是1而不是2。

解决这种内存不一致性的方法有两种

第一种是总线锁定。
CPU和其他部件的通信都是通过CPU总线来通信的,通过地址总线找到要操作的部件和使用数据总线来进行数据交互,当要进行写数据时,就在总线上发出一个LOCK#指令,表示其他CPU不可以再使用总线操作其他部件比如内存,同一时刻只有当前CPU可以访问内存,这样实现了对共享变量操作的原子性。

虽然保证了操作的原子性,但是由于我们只是需要对一个变量进行原子操作,却锁住了整个内存,阻塞了其他的CPU,因此降低了效率

第二种方法是使用缓存一致性协议MESI实现

MESI协议是以缓存行(缓存的基本单位 在intel的CPU上一般是64个字)的几个状态来命名的

M:Modified 被修改的
处于这一状态的数据,只能在本CPU中有缓存数据,其他CPU中都没有。相对内存中的值来说,是已经被修改的且没有被更新到主存中

E:Exclusive 独占的
处于这一种状态的数据 只有本CPU中有缓存 且其数据没有修改

S:Shared 共享的
处于该种状态的数据在多个CPU中都有缓存 且和内存一直

I:Invalid 无效的
本CPU中的这份缓存已经无效

每种状态对应的监听:
1. 一个处于M状态的缓存行 必须时刻监听所有视图读取该缓存行对应的主存地址的操作,如果监听到,就必须在此操作执行之前把缓存行中的数据写回CPU
2. 一个处于E状态的缓存行 必须时刻监听其他视图读取该缓存行对应的主存地址的操作,如果监听到,必须把缓存行状态设置为S
3. 一个处于S状态的缓存行 必须时刻监听使该缓存行无效或者独享该缓存行的请求,如果监听到,就把缓存行的状态设置为I

CPU需要读数据时:
1. 如果其缓存行状态是I,则需要从内存中读取并把自己的状态设置成S
2. 如果不是I,则可以直接从缓存中读取数值 但是在此之前 需要等待其他CPU的监听结果,如果其他CPU也有该数据的缓存且状态是M,则要等其他CPU把缓存更新要内存后再读;

CPU需要写数据时:
只有在状态是M或者E的时候才能执行,否则需要发出特殊的RFO指令(Read Or Ownership,这是一种总线事务),通知其他CPU置缓存无效(I),这种情况下会性能开销是相对较大的。在写入完成后,修改其缓存状态为M

所以如果一个变量在某段时间只被一个线程频繁地修改,则使用其内部缓存就完全可以办到,不涉及到总线事务,如果缓存一会被这个CPU独占、一会被那个CPU 独占,这时才会不断产生RFO指令影响到并发性能。这里说的缓存频繁被独占并不是指线程越多越容易触发,而是这里的CPU协调机制,这有点类似于有时多线程并不一定提高效率,原因是线程挂起、调度的开销比执行任务的开销还要大,这里的多CPU也是一样,如果在CPU间调度不合理,也会形成RFO指令的开销比任务开销还要大。当然,这不是编程者需要考虑的事,操作系统会有相应的内存地址的相关判断

并非所有情况都会使用缓存一致性的,如被操作的数据不能被缓存在CPU内部或操作数据跨越多个缓存行(状态无法标识),则处理器会调用总线锁定;

另外当CPU不支持缓存锁定时,自然也只能用总线锁定了,比如说奔腾486以及更老的CPU

原创粉丝点击