• 聊聊高并发(五)理解缓存一致性协议以及对并发编程的影响


    Java作为一个跨平台的语言。它的实现要面对不同的底层硬件系统,设计一个中间层模型来屏蔽底层的硬件差异,给上层的开发人员一个一致的使用接口。Java内存模型就是这样一个中间层的模型,它为程序猿屏蔽了底层的硬件实现细节,支持大部分的主流硬件平台。

    要理解Java内存模型以及一些处理高并发的技术手段,理解一些主要的硬件知识是必须的。

    这篇会说一下跟并发编程相关的一些硬件知识。


    一个主要的CPU运行计算的步骤例如以下:

    1. 程序以及数据被载入到主内存

    2. 指令和数据被载入到CPU的快速缓存

    3. CPU运行指令,把结果写到快速缓存

    4. 快速缓存中的数据写回主内存


    这个过程中,我们能够看到有两个问题

    1. 现代的计算芯片都会集成一个L1快速缓存,我们能够理解为每一个芯片都有一个私有的存储空间。

    那么当CPU的不同计算芯片要訪问同一个内存地址时,该内存地址的值会在CPU的不同计算芯片之间有多个拷贝,怎样同步这些拷贝

    2. CPU读写是直接和快速缓存打交道。而不是和主内存直接打交道。由于通常一次主存訪问在几十到几百个时钟周期。而一次L1快速缓存的读写仅仅须要1-2个时钟周期,而一次L2快速缓存的读写仅仅须要数十个时钟周期。

    那么CPU写到快速缓存的值何时写回到主内?假设是多个计算芯片在处理同一个内存地址。那么怎样处理这个时间差是个问题


    对于第一个问题。不同的硬件结构处理的方式不一样。我们来理解一下互连线的概念。

    互连线是处理器于主存以及处理器与处理器之间进行通信的媒介,有两种主要的互联结构:SMP(symmetric multiprocessing 对称多处理)和NUMA(nonuniform memory access 非一致内存訪问)

    SMP系统结构非常普通。由于它们最easy构建。非常多小型server採用这样的结构。处理器和存储器之间採用总线互联。处理器和存储器都有负责发送和监听总线广播的信息的总线控制单元。可是同一时刻仅仅能有一个处理器(或存储控制器)在总线上广播,全部的处理器都能够监听

    非常easy看出,对总线的使用是SMP结构的瓶颈。


    NUMP系统结构中,一系列节点通过点对点网络互联,像一个小型互联网,每一个节点包括一个或多个处理器和一个本地存储器。一个节点的本地存储对于其它节点是可见的,全部节点的本地存储一起形成了一个能够被全部处理器共享的全局存储器。能够看出,NUMP的本地存储是共享的,而不是私有的,这点和SMP是不同的。NUMP的问题是网络比总线复制。须要更加复杂的协议。处理器訪问自己节点的存储器速度快于訪问其它节点的存储器。NUMP的扩展性非常好,所以眼下非常多大中型的server在採用NUMP结构。


    对于上层程序猿来说。最须要理解的是互连线是一种重要的资源。使用的好坏会直接影响程序的运行性能。


    大概理解了不同的互连结构之后,我们来看看缓存一致性协议。它主要就是处理多个处理器处理同一个主存地址的问题。

    MESI是一种主流的缓存一致性协议,已经用在Pentium和PowerPC处理器中。它定义了缓存块的几种状态

    • modified(改动):缓存块已经被改动,必须被写回主存。其它处理器不能再缓存这个块
    • exclusive(相互排斥):缓存块还没有被改动。且其它处理器不能装入这个缓存块
    • share(共享):缓存块未被改动。且其它处理器能够装入这个缓存块
    • invalid(无效):缓存块中的数据无效

    上图展示了MESI快速缓存一致性协议的状态转换实例。

    1. 在a中,处理器A从地址a读取数据,将数据存入它的缓存并置为exclusive

    2. 在b中。当处理器B试图从同样地址a读取数据时。A检測到地址冲突,以相关数据做出响应。此时a同一时候被A和B以shared状态装入缓存

    3. 在c中,当B要对共享地址a进行写操作。则将状态改为modified,并广播提醒A,让它将它的缓存块状态设置为Invalid

    4. 在d中。当A试图从a读取数据,会广播它的请求。B则把它改动的数据发送到A和主存,并设置两个副本的状态为shared来做出响应


    很多其它缓存一致性协议的细节參考这篇 http://blog.csdn.net/realxie/article/details/7317630


    缓存一致性协议存在的一个最大的问题是可能引起缓存一致性流量风暴。之前我们看到总线在同一时刻仅仅能被一个处理器使用。当有大量缓存被改动,或者同一个缓存块一直被改动时,会产生大量的缓存一致性流量。从而占用总线。影响了其它正常的读写请求。


    一个最常见的样例就是假设多个线程对同一个变量一直使用CAS操作。那么会有大量改动操作。从而产生大量的缓存一致性流量,由于每一次CAS操作都会发出广播通知其它处理器,从而影响程序的性能。


    后面我们会讲怎样优化这样的使用方式。


    对于第二个问题,怎样处理改动数据从快速缓存到主内存的时间差。通常使用内存屏障来处理,后面会有专门的主题。


    转载请注明来源:http://blog.csdn.net/iter_zc



  • 相关阅读:
    如何访问到静态的文件,如jpg,js,css?
    内存定位
    虚拟机逃逸
    OpenGL
    测试
    unity3d
    磁力链接
    IDA脚本
    投屏神器
    扫二维码登录
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/5068812.html
Copyright © 2020-2023  润新知