《七周七并发模型》笔记+导图

1.0:概述

概念

  • 并发 :同一时间应对deal with多件事情的能力
  • 并行 :同一时间动手做doing多件事情的能力

并行架构

  • bit-level 位级并行
  • instruction-level 指令集并行
  • data 数据级并行
  • task-level 任务级并行

即多处理器架构-分类根据内存模型:共享内存、分布式内存

优势

及时响应,高效,容错(独立性与故障检测),简单。

2.0: 线程与锁

  • 本质:对底层硬件运行过程的形式化。

多线程基础(JAVA)

注意: 不应在产品代码中直接使用Thread等底层服务。

Java内存模型:定义了何时一个线程对内存的修改对另一个线程可见。

  • 危害: 产生竞态条件(互斥),死锁,内存可见性(内存模型)。
  • 对共享变量的所有访问同步化
  • 读线程与写线程同步化
  • 按照约定全局顺序获取多把锁
  • 当持有锁时避免调用外星方法
  • 持有锁的时间尽可能短。

JUC(locks&&atomic)

突破内置锁的性质 locks:ReentrantLock /atomic

atomic是lock-free&non-blocking算法的基础。

  • 在线程获取锁时中断它
  • 设置线程获取锁的超时时间
  • 按任意顺序获取和释放锁
  • 用条件变量等待某个条件变为真
  • 使用源自变量避免锁的使用。

ThreadPool等并发工具

生产者-消费者模式优点不仅在于可以并行的生产和消费,还可以存在多个生产者和、或多个消费者。

  • 使用线程池(ExecutorService),而不直接创建线程
  • 使用CopyOnWriteArrayList让监听器相关代码更简单有效
  • 使用ArrayBlockQueue让生产者和消费者高效协作
  • ConcurrentHashMap提高更好并发访问

Q: fork/join框架(JDK7+)与线程池的区别以及适用场景?

  • fork/join主要是解决Java社区中对于如synchronized,wait和notify等操作的低级别的话题活动的需求。Fork/Join框架被设计成可以容易地将算法并行化、分治化。开发人员曾多次想用自己(在非底层实现)的并发机制实现这一目标,因此新框架的想法是提供标准化和效率最高的并发工具协助开发人员实现各种多线程应用。其所需的类和接口都位于java.util.concurrent包中。
  • 使用ExecutorService和Callable的主要问题是,Callable实例在本质上是阻塞的。一旦一个Callable实例开始执行,其他所有Callable都会被阻塞。由于队列后面的Callable实例在前一实例未执行完成的时候不会被执行,因此许多资源无法得到利用。Fork/Join框架被引入来解决这一并行问题,而Executor解决的是并发问题(注:并发和并行的区别就是一个处理器同时处理多个任务和多个处理器或者是多核的处理器同时处理多个不同的任务)

总结

  1. 优点:适用面广,轻松实现,近似于对硬件工作方式的形式化。
  2. 缺点: 没有为并行提供直接支持,不适用于单个系统无法解决的问题。
  3. 可能错误: 难以测试(乱序执行会发生吗?)

Java是第一个定义内存模型的主流语言。

3.0: 函数式编程

  • 命令式编程: 代码由一系列改变全局状态的语句构成
  • 函数式编程:将计算过程抽象成表达式求值

抛弃可变状态(Clojure基础)

  • 用map或mapcat对一个序列每个元素进行映射
  • 用序列的懒惰特性处理较大序列,甚至无穷序列
  • 用reduce将序列化简化为一个可能比较复杂的值
  • 利用fold对reduce并行化

函数式并行(reducer框架)

  • pmap可以将映射操作并行化,构造一个半懒惰的map
  • 利用partition-all可以对并行的映射操作进行批处理,以提高效率
  • fold使用分而治之的策略,可以将化简操作并行化
  • clojure.core.reducers包提供的类似map,mapcat,filter的返回值并不是序列,二是化简器reducible,可以说这是化简操作的关键所在。

函数式并发(Future/Promise模型)

  • 引用透明性:在任何调用函数的地方,都可以用运行的结果来代替函数的调用,而不会对程序产生副作用。
  • Future/Promise模型:对数据流式编程提供支持(让代码在其所依赖的数据被准备好时才可以运行)

总结

  • 优点: 确信程序按照我们预想的方式进行,轻松并行化
  • Java8: stream API支持聚合操作,类似于Clojure的reducer,可以并行的处理流

4.0 Clojure之分离标识identity与状态state

原子变量与持久数据结构

Clojure是不纯粹的-为不同的开发场景提供了大量的可变量(atomic,etc);

  • 持久数据结构:数据结构在被修改时总是保留其之前的版本(Java中的CopyOnWriteArrayList就是),当一个线程修改它,将不会影响同一个数据结构的其他线程。
  • 命令式VS不纯粹的函数式:命令式状态容易改变,经常修改变量; 反之不容易改变,仅在必要时修改变量。

代理和软件事务内存(STM)(agent/ref)

  • 原子变量可以对单一值进行隔离的,同步的更新。
  • 代理可以对单一值进行隔离的,异步的更新。
  • 引用可以对多个值进行一致的,同步的更新。

原子变量orSTM

大部分问题都可以用原子变量来解决,更简单的方案通常会更有效。

  • 基于STM的解决方案可以被基与代理的解决方案替换
  • 定制并发函数可以使代码更简洁

总结

  • 优点: Clojure的持久数据结构将可变量的标识与状态分离开来,解决了使用锁的方案的大部分缺点。
  • 缺点: 不支持分布式编程,无法直接提供容错性,很多第三方库可以弥补(Akka-使用了actor模型)。

大部分主流语言都提供了STM的实现,但并不适合命令式语言。

5.0 Actor

  • 保留可变状态,只是不进行共享
  • 用Elixir(不纯粹,动态函数式,erlang变体)介绍
  • 容错性,失败检测,任其崩溃,分布式

创建Actor,消息和信箱

  • actor对象被称为进程,在Elixir中,进程是一个轻量级的概念,创建代价很低,通常不需要依赖线程池。
  • 用spawn()创建新县城
  • 用send()向进程发送消息
  • 用模式匹配来处理信息
  • 连接两个进程,结束时获得通知
  • 异步通信的基础上,实现双向的同步通信
  • 为进程命名

错误处理和容错性

  • 成熟的程序员通常使用尽可能小而简单的错误处理内核-小而简单到明显没有缺陷
  • 对一个使用actor模型的程序,其error-Kernel是顶层的管理者,管理着子进程
  • 使用actor模型的程序并不进行防御式编程,而是遵循“任其崩溃”的哲学,让actor的管理者来处理这些问题。
  • 连接是双向的
  • 连接可以传递错误

分布式

  • OPT(Open Telecom Platform)库解决问题

总结

  • 创建一个规模宏大可生长的系统的关键在于模块之间应该如何交流,而不再于其内部的属性和行为应该如何表现。
  • 我们可以认为actor模型是面向对象模型在并发编程领域的扩展。actor模型精心设计了消息传输和封装的机制,强调了面向对象的精髓。
  • 优点
    • 消息传输和封装
    • 容错
    • 分布式编程
  • 缺点 :死锁,信箱溢出问题,没有对并行提供直接支持
  • actor库: Akka-JVM语言

6.0 CSP模型(…)

  • actor模型重点在于参与交流的实体,CSP模型重点在于用于交流的通道channel(first class)
  • go语言 core.async库介绍

channel和go块

多个channel,go块

客户端CSP

总结

  • 优点:灵活性;异步IO/异步UI编程
  • 死锁,无并行支持

数据并行(…)

GPGPU(General-Purpose computing on the GPU)基于图形处理器的通用计算

  • 利用OpenCL可以挖掘出GPU的数据并行能力。

总结

-优点:适用于处理大量数值数据,尤其适合于科学计算etc;能耗低

8.0 Lambda架构

  • 大规模场景的角度解决问题
  • 侧重介绍批处理层Batch Layer和加速层Speed Layer
  • 批处理层适用MapReduce,延迟高,so增加加速层适用流处理等低延迟技术,合并两种视图就可以得到最终的计算结果。

批处理层和加速层

MapReduce原理

  • 定义:指代一类算法:对一个数据结构首先进行映射(map)操作,然后进行化简(reduce)操作,(更容易并行化)
  • 本质上说,lambda架构是将计算函数施加于大量数据的一种通用方法。

  • Hadoop优势:默认适用hadoop分布式文件系统(HDFS),这个有容错能力的系统可以在多个节点之间冗余数据。

批处理层

  • 传统数据系统缺陷

    • 扩展性(复制,分片等技术难以为续)
    • 维护成本、复杂度,人为错误
    • 报表和分析
  • 信息分类:原始信息(增加时间戮维持不变),衍生信息

  • 特性

    • 高度并行化,简单
    • 容错,支持分析
    • 缺点:延迟

加速层

  • Hadoop主要负责批处理,Storm主要负责实时视图(构建异步加速层)
  • Storm实时的处理元组流,元组由spout创建,由bolt处理,由topology调度

总结

  • 内容融合
    • 原始数据是永恒的抽象,让我们想到Clojure分离标识与状态的做法
    • Hadoop并行化解决问题是先将问题切分并映射到一个数据结构上,再进行化简操作,这非常类似于并行函数编程的做法。
    • 类似于actor模型,Lambda架构将处理过程分布到集群上,这样改进了性能,又可以对硬件故障进行容错
    • Storm的元组流类似于actor模型和CSP模型的消息机制
  • 优点:解决大规模数据问题、适合报表和分析(之前使用数据仓库)
  • 缺点: 计算规模要大
  • 批处理层可以用其他分布式批处理层实现(Spark)

9.0 回顾与未来

  • 本书学习了如何利用并发和并行来挖掘现代多核CPU的潜力
  • 学习elixir,storm,hadoop,进行分布式计算,从而创建出进行容错的解决方案

未来

  • 未来是“不变的”,无论是持久数据架构,还是Lambda的底层代码,actor发送的消息,即使是线程与锁模型(不可变的数据越多,需要使用的锁就越少,越不用担心内存可见性带来的问题)其核心思想是不变性。

  • 未来是分布式的-内存带宽危机。分布式内存。基于消息传递的技术,比如actor模型和CSP模型,会越来越重要。

  • Fork/Join模型和Work-Stealing算法,分治
  • 数据流
  • 反应型编程(or函数式)
  • 网格计算
  • 元组空间。

最后祝大家线程安全。

思维导图下载

MindNode