你真的懂原子性吗?

0 阅读4分钟

如果读者您对“原子性”的认知单纯只是一组操作要么连续做完,要么不做的话,那么说明您不是真的懂原子性,这篇文章值得您一读。

很多人(包括笔者自己)在刚接触并发领域时,通常会把 “连续性”等同于“原子性” ,认为对于一系列的操作,只要这一系列操作能直接执行完,在此期间不被打断,这一系列操作就是原子性的。其实这种看法是错误的。 这种情况只能说明这一系列操作具有“连续性”,而不具备“原子性”真正的原子性在具备连续性的同时,还应该具有“不可窥探性”。

“不可窥探性” 要求一个或是一系列的操作应该是 “一跃而就” 的。外界对于其内部的过程是不可窥探的,所以这就要求在执行原子性操作时,外界只能读到修改后的值,不可能读取到正在修改中的值。如果外界要访问一个被原子操作正在修改的值,那么应该等待这个原子操作结束

原子性的最终保障必须来自硬件,软件只能借助硬件原语来封装和扩展原子性边界。在软件层面讨论原子性是没有意义的,要讨论原子性, 我们必须把目光下放到硬件层面。那么硬件是怎么实现原子性的? 让我们走进CPU内部,看看真正的原子性实现

剖析 CPU 的原语看 CPU 怎么实现原子性

一. 连续性的实现: 在 CPU 的芯片外壳上通常有几根物理引脚是用来接受中断信号的,外部设备想中断 CPU 就给这根引脚发送一个高电平信号。CPU 内部有一个控制节点,它决定了中断是否能被响应,这个控制节点就是一个 “与门” ,有两个输入端:一个是中断引脚信号,另一个是中断屏蔽位。输出端为触发中断处理信号。当 CPU 执行原子操作时,将中断屏蔽位置为 0,在整个原子操作执行完后再置为 1。这就保证在整个过程中,CPU 都不会响应外部中断,直到原子操作结束。

二. 不可窥探性的实现: CPU 核心在执行原子性操作时会给指令加上 “lock”前缀,该前缀会触发 “缓存行锁定” (若数据跨缓存行则退化为锁总线)。通过 MESI 协议,该核心将对应缓存行标记为 ModifiedExclusive 状态,同时强制其他核心中对应的缓存行立即失效。在操作完成前,其他核心若尝试读取该数据,其请求会被“总线嗅探”机制拦截并等待,直到当前核心完成写入并结束锁定状态。这种机制确保了外界无法拿到正在被原子性修改的“中间态”数据。

注:要想彻底讲清楚 MESI 协议以及 CPU 的数据读取规则需要很长的上下文,笔者为了不跑题,这里在帮助理解的情况下粗略地讲解了运行逻辑。如果想深入探讨这方面知识,可以自行查阅资料或期待笔者即将发布的 《一篇文章带你精通volatile》。

原子性的权威定义为 “全有或全无”,无中间态、无部分执行;
聪明的您应该想到了,真正的原子性不仅要依靠不被中断来实现 “全有或全无,无部分执行” 的连续性,还需要 “互斥性” 来实现 “无中间态” 的不可窥探。很多人也喜欢把原子性当成互斥性来看待,其实这是不准确的,严格来说互斥性只是实现原子不可窥探性的一种手段。


结语:如果这篇文章能给读者您带来一些启发和思考,笔者将不胜荣幸。最后,笔者有个问题想留给各位,许多编程语言都宣称其锁机制能保障操作的“原子性”,您认为这种“原子性”是真正的“原子性”吗?欢迎在评论区分享你的观点。