美文网首页
并发中的陷阱-处理器重排序

并发中的陷阱-处理器重排序

作者: 蓝胖子_Android | 来源:发表于2018-01-24 18:10 被阅读0次

假设有两个线程分别调用同一个test对象的writer()和reader()。请问,b的值是什么?

(a) 1 

(b) 2 

(c) 1 or 2

publicclasstest{privateboolean flag =false;privateinta =0;publicvoidwriter(){            a =1;            flag = True;    }publicvoidreader(){if(flag){            b = a +1}    }}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

这里主要涉及的是处理器重排序问题。当前处理器为了加速指令执行,会将部分指令重排序之后执行。

数据依赖

数据依赖是一个简单的概念,就是判断前后两行代码在数据上有否有依赖关系。例如:

num1 =1 // (a)num2 =2 // (b)result= num1 + num2 // (c)

1

2

3

显然,c 语句用到的 num1 和 num2 依赖 a 和 b。

数据依赖分三种:

1 store - load

2 load - store

3 store - store

如何判断是否有依赖,很简单,只用判断两个语句之间是否用到同一个变量,是否是写操作。

Happen before

JVM定义了一个概念叫做 happen before,意思是前一条执行的结果要对后一条执行可见。简单来说前一条执行完,才能执行后一条。但实际上为了提高处理速度,JVM弱化了这个概念,在有数据依赖的情况下,前一条执行完,才能执行后一条。

看下面的例子:

num1 =1 // (a)num2 =2 // (b)result= num1 + num2 // (c)

1

2

3

对于上述三条语句 a, b, c执行,单线程顺序执行的情况。

ahappenbeforeb      b happenbeforec。

1

2

3

根据传递性可以得出:

ahappenbeforec

1

c指令要用到的 num1 和 num2 显然是依赖 a 和 b 的,典型的store-load。所以c指令必须等到 a 和 b 执行完才能执行。然而 a 和 b 并没有数据依赖,于是 JVM 允许处理器对 a 和 b 进行重排序。

a-> b -> c =3b-> a -> c =3

1

2

那么happen before到底是什么?我的理解是happen before是JVM对底层内存控制抽象出一层概念。我们可以根据代码顺序来判断happen before的关系,而JVM底层会根据实际情况执行不同的 action (例如添加内存屏障,处理器屏障,阻止重排序又或者是不做任何额外操作,允许处理器冲排序)。通过这一层使得内存控制对程序员透明,程序员也不需要考虑代码实际执行情况,JVM会保证单线程执行成功,as-if-serial。

既然JVM已经透明了内存控制,那为什么要搞清楚这点,那就是JVM只保证单线程执行成功,而多线程环境下,就会出各种各样的问题。

答案

下面就用上述讲的分析一下最初的题目。

A线程执行:

publicvoidwriter(){            a =1;// (1)flag = True;// (2)}

1

2

3

4

B线程执行:

publicvoidreader(){if(flag){// (3)b = a +1// (4)}    }

1

2

3

4

5

1.先考虑大多数人考虑的情况: 

指令顺序:(1)-> (2) -> (3) -> (4),b = 1 +1 = 2

2.意想不到的情况 

对于A线程来说,语句 (1)和(2)并不存在任何数据依赖问题。因此处理器可以对其进行重排序,也就是指令 (2)可能会先于指令(1)执行。 

那么当指令按照(2)-> (3) -> (4) -> (1) 顺序,b = 0 +1 = 1

3.还有一种情况 

对于B线程,处理器可能会提前处理 (4),将结果放到 ROB中,如果控制语句(3)为真,就将结果从ROB取出来直接使用,这是一种优化技术,预测。 

所以指令执行顺序可能是 (4) -> x -> x ->x

看来4条语句都有可能最先被执行。

总结一下,在多处理器环境中,由于每个处理器都有自己的读写缓存区,所以会使部分数据不一致。JMM会有一系列 action 保证数据一致性,但是在多线程环境下,还是会有很多诡异的问题发生,这个时候就要考虑处理器,编译器重排序。

相关文章

  • 并发中的陷阱-处理器重排序

    假设有两个线程分别调用同一个test对象的writer()和reader()。请问,b的值是什么? (a) 1 (...

  • WebFlux 响应式编程

    在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序。重排序分为编译器重排序和处理器重排序两种。•编译...

  • [Erlang开发之路]十三、并发程序中的错误处理

    一、并发程序错误处理理念 Erlang并发编程中错误处理的概念,核心在于远程检测加处理错误(而非顺序编程中的避免错...

  • iOS多线程随笔

    1. 多线程的并发控制 1.1 在CGD中快速实现多线程的并发控制 NSOperationQueue来处理并发控制...

  • dispatch_semaphore信号量用法

    关于多线程的并发,我们可能会选择使用NSOperationQueue来处理,那么在GCD中如何处理多线程的并发呢?...

  • java内存模型

    《深入理解java内存模型》-笔记 java各线程共享使用主内存,通过共享内存通信 重排序重排序.png编译器重排...

  • 大数据入门学习

    常用处理方案 对于大量数据的处理方案:大量数据处理。 对于高并发的处理方案:高并发处理。 ============...

  • 排序

    排序在内存中处理,不涉及数据的内外存交换,称为内部排序,反之为外部排序。内部排序又分为五类:插入、交换、选择、归并...

  • 浅谈JavaWeb中高并发业务处理

    文章地址:浅谈JavaWeb高并发业务处理 浅谈JavaWeb中高并发业务处理 在JavaWeb应用中高并发的业务...

  • Go使用goroutine并发的快速排序

    经典面试题:快速排序。一般都使用递归,但golang中利用goroutine的并发可以加快。

网友评论

      本文标题:并发中的陷阱-处理器重排序

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