聊聊原子变量、锁、内存屏障那点事(1).doc

聊聊原子变量、锁、内存屏障那点事(1).doc

ID:28121048

大小:193.50 KB

页数:7页

时间:2018-12-08

聊聊原子变量、锁、内存屏障那点事(1).doc_第1页
聊聊原子变量、锁、内存屏障那点事(1).doc_第2页
聊聊原子变量、锁、内存屏障那点事(1).doc_第3页
聊聊原子变量、锁、内存屏障那点事(1).doc_第4页
聊聊原子变量、锁、内存屏障那点事(1).doc_第5页
资源描述:

《聊聊原子变量、锁、内存屏障那点事(1).doc》由会员上传分享,免费在线阅读,更多相关内容在学术论文-天天文库

1、聊聊原子变量、锁、内存屏障那点事(1)突然想聊聊这个话题,是因为知乎上的一个问题多次出现在了我的Timeline里:请问,多个线程可以读一个变量,只有一个线程可以对这个变量进行写,到底要不要加锁?可惜的是很多高票答案语焉不详,甚至有所错漏。所以我想在这篇文章里斗胆聊聊这个水挺深的问题。受限于个人水平,文章若有错漏,还望读者不吝赐教。首先约定,由于CPU的架构和设计浩如烟海,本文站在工程师的角度,只谈IA32/AMD64(x86-64)架构,不讨论其他架构的细节和差异。并且文章中主要引用Intel的

2、文档予以佐证,不关注AMD在实现细节上的差异。众所周知,当一个执行中的程序的数据被多个执行流并发访问的时候,就会涉及到同步(Synchronization)的问题。同步的目的是保证不同执行流对共享数据并发操作的一致性。早在单核时代,使用锁或者原子变量就很容易达成这一目的。甚至因为CPU的一些访存特性,对某些内存对齐数据的读或写也具有原子的特性。比如,在《Intel®64andIA-32ArchitecturesSoftwareDeveloper’sManual》的第三卷SystemPr

3、ogrammingGuide的Chapter8Multiple-ProcessorManagement里,就给出了这样的说明:也就是说,有些内存对齐的数据的访问在CPU层面就是原子进行的(注意这里说的只是单次的读或者写,类似普通变量i的i++操作不止一次内存访问)。此时,环形队列(Ringbuffer)这种数据结构在某些架构的单核CPU上,只有一个Reader和一个Writer的情况下是不需要额外同步措施的。原因就是read_index和writer_index的写操作在满足对齐内存访问的情况下是

4、原子的,不需要额外的同步措施。注意这里我加粗了单核CPU这个关键字,那么到了多核心处理器的今天,该操作就不是原子了吗?不,依旧是原子的,但是出现了其他的干扰因素迫使可能需要额外的同步措施才能保证原本无锁代码的正确运行。首先是现代编译器的代码优化和编译器指令重排可能会影响到代码的执行顺序。编译期指令重排是通过调整代码中的指令顺序,在不改变代码语义的前提下,对变量访问进行优化。从而尽可能的减少对寄存器的读取和存储,并充分复用寄存器。但是编译器对数据的依赖关系判断只能在单执行流内,无法判断其他执行流对竞

5、争数据的依赖关系。就拿无锁环形队列来说,如果Writer做的是先放置数据,再更新索引的行为。如果索引先于数据更新,Reader就有可能会因为判断索引已更新而读到脏数据。那禁止编译器对该类变量的优化,解决了编译期的重排序就没事了吗?不,CPU还有乱序执行(Out-of-OrderExecution)的特性。流水线(Pipeline)和乱序执行是现代CPU基本都具有的特性。机器指令在流水线中经历取指、译码、执行、访存、写回等操作。为了CPU的执行效率,流水线都是并行处理的,在不影响语义的情况下。处理器

6、次序(ProcessOrdering,机器指令在CPU实际执行时的顺序)和程序次序(ProgramOrdering,程序代码的逻辑执行顺序)是允许不一致的,即满足As-if-Serial特性。显然,这里的不影响语义依旧只能是保证指令间的显式因果关系,无法保证隐式因果关系。即无法保证语义上不相关但是在程序逻辑上相关的操作序列按序执行。从此单核时代CPU的Self-Consistent特性在多核时代已不存在,多核CPU作为一个整体看,不再满足Self-Consistent特性。简单总结一下,如果不做多

7、余的防护措施,单核时代的无锁环形队列在多核CPU中,一个CPU核心上的Writer写入数据,更新index后。另一个CPU核心上的Reader依靠这个index来判断数据是否写入的方式不一定可靠。index有可能先于数据被写入,从而导致Reader读到脏数据。所有的麻烦到这里就结束了吗?当然不,还有Cache的问题。前文提到的都是顺序一致性(SequentialConsistency)的问题,没有涉及Cache一致性(CacheCoherence)的问题。虽然说一般情况下程序员只需要关注顺序一致性

8、即可,但是区分清楚这两个概念也能更好的解释内存屏障(MemoryBarrier)。开始提到Cache一致性协议之前,先介绍两个名词:Load/ReadCPU读操作,是指将内存数据加载到寄存器的过程Store/WriteCPU写操作,是指将寄存器数据写回主存的过程现代处理器的缓存一般分为三级,由每一个核心独享的L1、L2Cache,以及所有的核心共享L3Cache组成:由于Cache的容量很小,一般都是充分的利用局部性原理,按行/块来和主存进行批量数据交换,以提升数据的访问效率。以前

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。