i++问题   

  

  “阿q,快点回来。隔壁二车间的老虎说我们改了他们的数据,找上门来闹事。”   

  

  由于老k的突然出现,我不得不提前结束与小黑的交流,赶回CPU一号车间。   

  

  当我回来的时候,老虎立刻冲我大喊,“你怎么了?只花了几纳秒就给我改了数据。告诉我该怎么办!”   

  

  我听得迷迷糊糊,一遍又一遍地说:“别着急,老虎。我刚回来。这是怎么回事?先通知我一声,好吗?”   

  

     

  

  接下来,老k告诉了我事情的经过。原来负责我们两个CPU车间的线程都在执行一个I操作,我们都把I的值放在自己的缓存里。完成后,我们没有通知对方。我们加了两次,结果只有一次,导致数据不一致。   

  

  原子操作知道事情的全部经过后,我对你说:“每个人执行的代码都是一样的,这不能怪我们。”   

  

  你很焦虑。“你为什么不责怪我们?我们在你之前找到并取走了记忆。然后你要等到我们加完。不信你可以打电话给记忆佬,看看我们2号车间是不是先来的。”   

  

  “好了,好了,先冷静下来。你看,我们不知道你先去拿了。这是不可原谅的吗?再说,既然事情出了差错,我们就应该一起坐下来,想办法避免以后再出现这种问题,你说呢?”   

  

  老虎叹了口气,问道:“那你告诉我你能做什么?”   

  

     

  

  我继续说,“看,我们不应该被打扰,当我们像我一样执行操作时”   

  

  “没被打扰?”   

  

  “对,比如你们二车间参观我的时候,我们一车间参观不了,只好等。你参观后我们会回来,但这个非常简单的方法非常有用。”   

  

  虎子听后一愣,“这不是锁吗?要不要怪程序员在做I之前不锁?”   

  

  “确实是锁定,但是程序员锁定这个简单的操作太麻烦了。我们就在CPU内部处理吧。”   

  

  “内部处理,你打算怎么实现?”,虎子问道。   

  

  “来,让我考虑一下”,你问的是具体实施,我还没想到这一步。   

  

  这时,一旁的老k站了起来:“我确实有办法找到公交局长。他是总指挥,负责协调各车间使用系统总线访问内存。他在中间协调应该不难。”   

  

  古语“K”惊醒梦中人,然后我们去找了公交局长。后来我们讨论了一套解决方案:我们定义了一个叫原子操作的东西,意思是它是一个不可分的动作。谁要执行原子操作,总线指挥器就在系统总线上加一个LOCK#信号,其他车间的人要访问内存就得等到原子操作指令执行完。   

  

     

  

  我们把这个方案上报给了领导,很快得到了批准。后来我们八个车间都是按照这个方案工作的。程序员把I这样的动作改成原子操作后,问题就可以解决了。   

  

  但实施一段时间后,各个车间开始大受其害:就因为一个车间要进行一次原子操作,就要求总线主管锁定系统总线,其他车间的人无法访问内存,无法工作,严重影响了工作效率。   

  

  抱怨就是抱怨,在没有更好的替代方案之前,生活还得继续。   

  

  然而,缓存引发的问题,没过多久,数据不一致的情况再次出现。   

  

  这次不是加法的问题。我们两个车间因为各自缓存的原因修改了变量值。对方没有马上知道,误用了错误的价值观,酿成大错。   

  

     

  

  “阿q,上次那个办法不错,可以解决。   

不了这一次的问题啊”,虎子再次找上门来。

  

“你来的正好,我正想去找你说这事呢”

  

“哦,是吗,难不成你想到破解之道了?”

  

“只是一些初步的想法,问题的核心在于现在咱们各个车间各自为政,都有自己的私有缓存,各自修改数据后向内存更新时也不互相打招呼,缺少一个联络机制”

  

虎子点了点头,“确实,所以咱们需要建立一个联络机制,来对各个车间的缓存内容进行统一管理是吗?”

  

“对!这事儿咱俩说了可不算,我建议召集8个核心车间的代表,统一开一个会议,详细讨论下这个问题。哦,对了,把总线主任也叫上,他经验丰富说不定能提供一些思路”

  

缓存一致性协议MESI很快,咱们CPU的8个核心车间就为此问题召开了会议,并且取得了非常重要的成果。

  

我们牵了一条新的专线,把8个核心车间连接起来,用于各个车间之间进行信息沟通,不同于CPU外部的总线系统,大家把这个叫片内总线。

  

  

新的线路铺设好了,以后大家就可以通过这条线路即时沟通,为了解决之前出现的问题,大家还制定了一套规则,叫做缓存一致性协议。

  

规则里面规定了所有车间的缓存单元――缓存行有四种状态:

  

已修改Modified (M)缓存行已经被修改了,与内存的值不一样。如果别的CPU内核要读内存这块数据,要赶在这之前把该缓存行回写到主存,把状态变为共享(S).

  

独占Exclusive (E)缓存行只在当前CPU核心缓存中,而且和内存中数据一样。当别的CPU核心读取它时,状态变为共享;如果当前CPU核心修改了它,就要变为已修改状态。

  

共享Shared (S)缓存行存在于多个CPU核心的缓存中,而且和内存中的内容一致。

  

无效Invalid (I)缓存行是无效的

  

四种状态之间的转换是这样的:

  

  

按照这套规则,大家不能再像以前那样随意了,各车间对自家缓存进行读写时,都要相互通一下气,避免使用过时的数据。

  

除此之外,还规定如果一块内存区域被多个车间都缓存,就不再允许多个车间同时去修改缓存了。

  

会议还有另外一个收获,以前被各车间诟病的每次原子操作都要锁定总线,导致大家需要访问内存的都只能干等着的问题也得到了解决。以后总线主任不再需要锁定总线了,通过这次的缓存一致性协议就可以办到。

  

自此以后,数据不一致的问题总算是根治了,咱们8个车间又可以愉快的工作了。

  

作者:轩辕之风

  

来源:编程技术宇宙公众号