第一章 走入并行世界

来源:互联网 发布:mac安装什么虚拟机 编辑:程序博客网 时间:2024/04/30 14:55

  • 第一章 走入并行世界
    • 2 你必须知道的几个概念
      • 21 同步与异步
        • 22 并发Concurrency与并行Parallelism
        • 23 临界区
        • 24 阻塞Blocking与非阻塞Non-Blocking
        • 25 死锁Deadlock饥饿Starvation和活锁Livelock
    • 3 并发级别
      • 31 阻塞Blocking
      • 32 无饥饿Starvation-Free
      • 33 无障碍Obstruction-Free
      • 34 无锁Lock-Free
      • 35 无等待Waite-Free
    • 4 有关并行的两个重要定律
      • 41 Amdahl
      • 42 Gustafson
      • 43 Amdahl定律和Gustafson定律是否相互矛盾
    • 5 回到JavaJMMJava内存模型
      • 51 原子性Atomicity
      • 52 可见性Visibility
      • 53 有序性Ordering
      • 54 哪些指令不能重排Happen-Before规则

第一章 走入并行世界

1.2 你必须知道的几个概念

1.2.1 同步与异步

1.2.2 并发(Concurrency)与并行(Parallelism)

  • 并发偏重于多个任务交替执行,而多个任务之间有可能存在还是串行的
  • 并行是真正意义上的同时执行
  • 如果只有一个cpu是不可能真实并行的。

1.2.3 临界区

共享资源

1.2.4 阻塞(Blocking)与非阻塞(Non-Blocking)

1.2.5 死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)

都属于多线程活跃问题
- 死锁:最糟糕的一种情况
- 饥饿是指某一个或多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。如优先级太低。饥饿还是有可能在未来的一段时间内解决的。
- 活锁:秉承着“谦让”的原则,主动将资源释放给他人使用,那么就会出现资源不断在两个线程中跳动,而没有一个线程可以同时获得所有资源而正常执行。

1.3 并发级别

1.3.1 阻塞(Blocking)

synchronized或重入锁实现。

1.3.2 无饥饿(Starvation-Free)

锁是公平的,满足先来后到。即没有优先级的概念,从而避免低优先级等待导致饥饿

1.3.3 无障碍(Obstruction-Free)

最弱的非阻塞调度,可以同时进入临界区,如果出现数据修改出问题,一旦检测到,它会立即对自己所做的修改回滚,确保数据安全。是一种乐观的策略,它认为多个线程之间很有可能不会发生冲突

1.3.4 无锁(Lock-Free)

  • 无锁的并行都是无障碍的。无锁并发保证必然有一个线程可以在有限步内完成操作离开临界区。
  • 一个典型的特点就是包含一个无限循环,线程会不断的尝试修改共享变量,如果运气不好,一直失败,则会出现类似饥饿的现象。

1.3.5 无等待(Waite-Free)

  • 要求所有线程必须在有限步内完成操作离开临界区,这样便不会出现饥饿问题。如果限制这个步骤上限,还可以进一步分解为有界无等待和线程数无关的无等待几种,它们之间的区别只是对循环次数的限制不同。
  • 一种典型的无等待结构就是RCU(Read-Copy-Update),读数据读不加控制,即无等待,对写数据,先取得原数据副本,再修改副本,修改完成后,在合适的时机回写数据。

1.4 有关并行的两个重要定律

衡量串行程序改造并行程序,性能提高度。

1.4.1 Amdahl

  • 定义了串行系统并行化后的加速比的计算公式和理论上限
    • 加速比定义:加速比 = 优化前系统耗时 / 优化后系统耗时
    • Amdahl公式推导过程:n表示处理器个数,T表示时间,T1表示优化前耗时,也就是1个处理器耗时,Tn表示使用n个处理器优化后的耗时。F是程序中只能串行执行的比例。
    • Tn = T1(F + 1/n(1-1/F))
    • 加速比=T1/Tn=1/(F + 1/n(1-F))
    • 根据公式,如果cpu处理器数趋于无穷,那么加速比与系统的串行率成反比,如系统中必须有50%的代码串行执行,那么系统最大的加速比为2;
    • 由此可见为了提高系统的速度,仅增加cpu处理器的数量并不一定能起到有效的作用。

1.4.2 Gustafson

  • 从另一个角度说明处理器个数、串行比例和加速比之间的关系
    • 执行之间:串行时间a + 并行时间b
    • 总执行时间:a + n(处理器个数)b
    • 加速比 = (a + nb)/(a + b)
    • 定义串行比例 F = a/(a + b)
    • 则加速比
S(n)  = (a + nb)/(a + b)        = a /(a + b) + nb/(a + b)        = F+n((a+b-a)/a +b)        = F + n(1 - a/(a + b))        = F+n(1-F        = F + n - nF        = n-F(n - 1
  • 根据公式,如果串行比例很小,并行比例很大,那么加速比就等于处理器个数,因此只要不断增加处理器个数,就能获得更快的速度

1.4.3 Amdahl定律和Gustafson定律是否相互矛盾

对同一个客观事实从不同角度去审视后的结果。
- Amdahl强调:当串行比例一定时,加速比是有上限的,不管你堆叠多少个cpu参与计算,都不能突破这个上限
- Gustafson强调:如果可被并行化的代码所占比重足够多,那么加速比就能随着cpu数量线性增长

1.5 回到Java:JMM(Java内存模型)

JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的。

1.5.1 原子性(Atomicity)

  • 是指一个操作是不可中断的。一个操作一旦开始,就不会被其他线程干扰。
  • 32位系统,long的读写都不是原子性的,线程之间会互相干扰

1.5.2 可见性(Visibility)

  • 是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。
  • 缓存优化、硬件优化、指令重排、编辑器优化,都有可能导致一个线程修改不会立即被其他线程察觉。

1.5.3 有序性(Ordering)

  • 指令重排,导致了指令与原指令的顺序未必一致。
  • 指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。

1.5.4 哪些指令不能重排:Happen-Before规则

  • 程序顺序原则:一个线程内保证语义的串行性
  • volatile规则:volatile变量的写,先发生于读
  • 锁规则:解锁必然发生在随后的加锁前
  • 传递性:A先于B,B先于C,A必然先于C
  • 线程的start方法先于它的每一个动作
  • 线程的所有操作先于线程的终结(Thread.join())
  • 线程的中断(interrupt())先于被中断线程的代码
  • 对象的构造函数执行、结束先于finalize()方法