随着多核时代的到来,并发操做已经成了很正常的现象,操做系统必需要有一些机制和原语,以保证某些基本操做的原子性,好比处理器须要保证读一个字节或写一个字节是原子的,那么它是如何实现的呢?有两种机制:总线锁定和缓存一致性。下面,尚学堂陈老师简单为你们分享总线锁定和缓存一致性的问题。
咱们知道,CPU和物理内存之间的通讯速度远慢于CPU的处理速度,因此CPU有本身的内部缓存,根据一些规则将内存中的数据读取到内部缓存中来,以加快频繁读取的速度。咱们假设在一台PC上只有一个CPU和一分内部缓存,那么全部进程和线程看到的数都是缓存里的数,不会存在问题;但如今服务器一般是多 CPU,更广泛的是,每块CPU里有多个内核,而每一个内核都维护了本身的缓存,那么这时候多线程并发就会存在缓存不一致性,这会致使严重问题。
以 i++为例,i的初始值是0.那么在开始每块缓存都存储了i的值0,当第一块内核作i++的时候,其缓存中的值变成了1,即便立刻回写到主内存,那么在回写以后第二块内核缓存中的i值依然是0,其执行i++,回写到内存就会覆盖第一块内核的操做,使得最终的结果是1,而不是预期中的2.前端
那么怎么解决整个问题呢?操做系统提供了总线锁定的机制。前端总线(也叫CPU总线)是全部CPU与芯片组链接的主干道,负责CPU与外界全部部件的通讯,包括高速缓存、内存、北桥,其控制总线向各个部件发送控制信号、经过地址总线发送地址信号指定其要访问的部件、经过数据总线双向传输。在CPU1要作 i++操做的时候,其在总线上发出一个LOCK#信号,其余处理器就不能操做缓存了该共享变量内存地址的缓存,也就是阻塞了其余CPU,使该处理器能够独享此共享内存。
但咱们只须要对此共享变量的操做是原子就能够了,而总线锁定把CPU和内存的通讯给锁住了,使得在锁按期间,其余处理器不能操做其余内存地址的数据,从而开销较大,因此后来的CPU都提供了缓存一致性机制,Intel的奔腾486以后就提供了这种优化。
缓存一致性机制总体来讲,是当某块CPU对缓存中的数据进行操做了以后,就通知其余CPU放弃储存在它们内部的缓存,或者从主内存中从新读取,以下图:编程
这里以在Intel系列中普遍使用的MESI协议详细阐述下其原理。
MESI协议
MESI 协议是以缓存行(缓存的基本数据单位,在Intel的CPU上通常是64字节)的几个状态来命名的(全名是Modified、Exclusive、 Share or Invalid)。该协议要求在每一个缓存行上维护两个状态位,使得每一个数据单位可能处于M、E、S和I这四种状态之一,各类状态含义以下:
M:被修改的。处于这一状态的数据,只在本CPU中有缓存数据,而其余CPU中没有。同时其状态相对于内存中的值来讲,是已经被修改的,且没有更新到内存中。
E:独占的。处于这一状态的数据,只有在本CPU中有缓存,且其数据没有修改,即与内存中一致。
S:共享的。处于这一状态的数据在多个CPU中都有缓存,且与内存一致。
I:无效的。本CPU中的这份缓存已经无效。
这里首先介绍该协议约定的缓存上对应的监听:
一个处于M状态的缓存行,必须时刻监听全部试图读取该缓存行对应的主存地址的操做,若是监听到,则必须在此操做执行前把其缓存行中的数据写回CPU。
一个处于S状态的缓存行,必须时刻监听使该缓存行无效或者独享该缓存行的请求,若是监听到,则必须把其缓存行状态设置为I。
一个处于E状态的缓存行,必须时刻监听其余试图读取该缓存行对应的主存地址的操做,若是监听到,则必须把其缓存行状态设置为S。
当CPU须要读取数据时,若是其缓存行的状态是I的,则须要从内存中读取,并把本身状态变成S,若是不是I,则能够直接读取缓存中的值,但在此以前,必需要等待其余CPU的监听结果,如其余CPU也有该数据的缓存且状态是M,则须要等待其把缓存更新到内存以后,再读取。
当CPU须要写数据时,只有在其缓存行是M或者E的时候才能执行,不然须要发出特殊的RFO指令(Read Or Ownership,这是一种总线事务),通知其余CPU置缓存无效(I),这种状况下会性能开销是相对较大的。在写入完成后,修改其缓存状态为M。
因此若是一个变量在某段时间只被一个线程频繁地修改,则使用其内部缓存就彻底能够办到,不涉及到总线事务,若是缓存一会被这个CPU独占、一会被那个CPU 独占,这时才会不断产生RFO指令影响到并发性能。这里说的缓存频繁被独占并非指线程越多越容易触发,而是这里的CPU协调机制,这有点相似于有时多线程并不必定提升效率,缘由是线程挂起、调度的开销比执行任务的开销还要大,这里的多CPU也是同样,若是在CPU间调度不合理,也会造成RFO指令的开销比任务开销还要大。固然,这不是编程者须要考虑的事,操做系统会有相应的内存地址的相关判断,这不在本文的讨论范围以内。
并不是全部状况都会使用缓存一致性的,如被操做的数据不能被缓存在CPU内部或操做数据跨越多个缓存行(状态没法标识),则处理器会调用总线锁定;另外当CPU不支持缓存锁定时,天然也只能用总线锁定了,好比说奔腾486以及更老的CPU。
缓存