美文网首页
有关并发编程

有关并发编程

作者: 34sir | 来源:发表于2018-02-09 16:41 被阅读9次

    内存模型

    在讲述并发编程之前,我么首先要先了解内存模型

    计算机执行指令,每条指令都在cpu中执行
    cpu执行速度很快,内存的读写相对慢 ,因此cpu中有高速缓存
    当程序在运行过程中,会将运算需要的数据从主存复制一份到cpu的高速缓存当中,cpu直接从里面读取数据,计算结束后将数据刷新到主存中

    举个简单的栗子:

    i = i + 1;
    

    当线程执行这段语句时会分为5步:

    • 先从主存当中读取i的值
    • 然后复制一份到高速缓存当中
    • 然后cpu执行指令对i进行加1操作
    • 然后将数据写入高速缓存
    • 最后将高速缓存中i最新的值刷新到主存当中

    上述语句的执行在多线程中存在缓存一致性的问题:
    多核cpu中,每条线程可能运行于不同的cpu中,因此每个线程运行时有自己的高速缓存,由此,上述语句在多线程中变量i在多个cpu中都存在缓存,这时就出现了缓存一致性的问题
    被多个线程访问的变量为共享变量

    那么,如何解决缓存一致性的问题?
    两种方式:

    • 通过在总线加LOCK#锁的方式
    • 通过缓存一致性协议

    这两种方式其实都是通过硬件层面来处理的

    由于在总线加LOCK#锁的方式会使得效率低下,所以出现了后来的缓存一致性协议
    最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的
    核心思想:
    当cpu写数据时,如果发现操作的变量是共享变量,即在其他cpu中也存在该变量的副本,会发出信号通知其他cpu,将该变量的缓存行置为无效状态,因此当其他cpu需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取

    并发编程

    ok,了解完内存模型后我们来看并发编程

    并发编程中我们通常会遇到三个问题:

    • 原子性问题
    • 可见性问题
    • 有序性问题

    原子性

    一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行

    怎么理解?举一个简单的栗子:

    int i;
    i = i + 1;
    

    前面我们已经知道这段代码的执行需要分五个步骤,假设在第一步的时候代码执行的操作突然间断,此时另外一个线程正好读取i的值,那么取到的是0而非期望的1,由此可知并发编程时需要满足原子性

    可见性

    可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
    举个简单的栗子:

    // 线程1
    int i;
    i = i + 1;
    
    // 线程2
    int j = i;
    

    线程1中执行完i = i + 1;代码时,会将cpu1中的高速缓存当中i的值变为1,这时并没有立即刷新主存中i的值,此时线程2恰好执行int j = i;取到的i值还是主存中的0并不是期望的1。线程2没能立即看到线程1中修改共享变量之后的值,这就是并发编程中的可见性问题

    有序性

    程序执行的顺序按照代码的先后顺序执行
    举一个简单的栗子:

    int i = 0;              
    boolean flag = false;
    i = 1;                //语句1  
    flag = true;          //语句2
    

    上述代码,语句1是在语句2之前,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?不一定,为什么?这里出现了一个新的概念:指令重排序

    指令重排序:处理器为了提高效率,可能会对代码进行优化,它不保证各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的
    简单的讲,为了效率会对代码执行顺序重排并且不影响结果

    这里就会有另外一个问题:靠什么保证结果的一致性?
    举一个简单的栗子:

    int a = 10;    //语句1
    int r = 2;    //语句2
    a = a + 3;    //语句3
    r = a*a;     //语句4
    

    其中一个可能的执行顺序:
    2-->1-->3-->4
    那么
    2-->1-->4-->3
    这种顺序有可能吗?

    不可能,处理器在指令重排时会考虑数据的依赖性,栗子中的语句4依赖了语句3,那么它就会保证这两句的顺序执行来保证结果的一致

    上述的考虑是在单线程的情况下,那么多线程呢?
    来,再吃一个栗子:

    //线程1:
    context = loadContext();   //语句1
    inited = true;             //语句2
     
    //线程2:
    while(!inited ){
      sleep()
    }
    doSomethingwithconfig(context);
    

    语句1和语句2没有相互依赖,那么就有可能发生2先于1执行,那么紧接着线程2执行,此时会跳过while循环直接执行doSomethingwithconfig(context);,那么此时context是null就会出现问题了

    所以,并发编程中会存在有序性的问题

    总结

    要想并发程序正确地执行,必须要保证原子性、可见性以及有序性,三者缺一不可

    相关文章

      网友评论

          本文标题:有关并发编程

          本文链接:https://www.haomeiwen.com/subject/xdittftx.html