七周七并发模型 第五六七章笔记

date
Jul 16, 2022
slug
七周七并发模型 第五六七章笔记
status
Published
tags
读书笔记
summary
Actor模型&CSP模型&Lambda架构
type
Post

第五章 Actor

actor模型可以应用于共享内存架构和分布式内存架构,适合解决地理分布型的问题。同时提供了很好的容错性。
actor模型保留了可变状态,只是不进行共享。actor类型面向对象中的对象——其封装了状态,并通过消息与其他actor通信。所有的actor可以同时运行,并且actor之间的消息传递是真实的在传递消息
异步地发送消息是actor模型的重要特性之一。消息并不是直接发送到一个actor,而是发送到一个信箱,这解耦了actor之间的关系——actor都以自己的步调运行,且发送消息时不会被阻塞,虽然所有的actor可以同时运行,它们都按照信箱接受消息的顺序来依次处理消息,且仅当当前消息处理完后才会处理下一个消息。
彻底关闭一个actor,需要满足两个条件:
  • 需要告诉actor在完成消息处理后就关闭
  • 需要知道actor何时完成关闭
有状态的actor
可以使用递归函数的参数代替可变量作为状态。
双向通信
actor模型没有提供直接回复消息的机制,但可以通过将发送进程的标识符包含在消息中来使消息的接收者可以回复消息。
为进程命名
如果要向一个别人创建的进程发送消息,最简单的方法就是为进程命名。
Process.register()为进程命名,Process.whereis()按名称查找进程。Process.registered()可以查看已被命名的所有进程
错误检测
Elixir提供了一种方法——将错误处理隔离到一个管理进程中。
Process.link()在两个进程之间建立连接。连接是双向的,如果其中一个进程非正常终止,那么两个进程就都终止了,而进程正常终止不会让连接的另一个进程终止的。
系统进程
通过设置进程的:trap_exit标识,可以让一个进程捕获另一个进程的终止消息,专业术语来说,就是将进程转化为系统进程
管理进程
可以通过进程管理者(是一个系统进程),它管理着若干个工作进程,当工作进程崩溃时进行干预。
超时
超时机制防止死锁
消息是否保证能被送达
Elixir有两个规则:
  • 如果没有异常发生,消息一定能被送达并被处理
  • 如果某个环节出现异常,异常一定会通知到使用者(假设使用者已经连接到或正在管理发生异常的进程)。这条规则是Elixir提供容错性的基石
错误处理内核模式
软件设计有两种方式:
  • 使软件过于简单,明显的没有缺陷
  • 使软件过于复杂,没有明显的缺陷
一个软件如果应用了错误处理内核模式,那么该系统正确运行的前提是错误处理内核必须正确运行。成熟的程序通常使用尽可能小而简单的错误处理内核——小而简单到明显美誉缺陷。
任其崩溃
使用actor模型的程序并不进行防御式编程,而是遵循“任其崩溃”的哲学,让actor的管理者来处理这些问题。
OTP
GenServer是OTP的一个组件,是一个行为,可以用来自动创建一个有状态的actor。
OTP管理者行为支持多种不同的重启策略,常用两种:
  • One-for-all:如果一个工作进程崩溃,使用one-for-all策略的管理者将重启所有工作进程(包括那些没有崩溃的工作进程)
  • One-for-one:仅重启已经崩溃的工作进程
OTP的优点:
  • 更好的重启逻辑
  • 调试与日志
  • 代码热升级
  • 发布管理、故障切换、自动扩容等
节点
每创建一个Erlang虚拟机实例,就相当于创建了一个节点。
Node.self()查看节点名称,Node.list()查看当前节点已知的其他节点列表,Node.connect()连接其他节点。连接是双向的,第二台计算机也知道第一台的信息。
:global.register_name()在集群全局注册名字。:global.whereis_name()获取进程标识符。消息的运行结果会输出到产生消息的actor的父进程节点上
面向对象编程真正主要的方面是“消息”。创建一个规模宏大且可生长的系统的关键在于其模块之间应该如何交流,而不在于其内部的属性和行为应该如何表现
actor模型的缺点:
  • 仍会碰到死锁问题
  • 也会有一些actor模型独有的问题(比如信箱溢出)
  • 没有对并行提供直接支持,需要通过并发的技术来构造并行的方案,这样就会引入不确定性。而且,由于多个actor并不共享状态,仅通过消息传递来进行交流,所以不太适合实施细粒度的并行

第六章 通信顺序进程 CSP

CSP模型也是友独立的、并发执行的实体组成,实体之间也是通过发送消息进行通信,CSP模型不关注发送消息的实体,而是关注发送消息时使用的channel。channel是第一类对象,它不像进程那样与信箱是紧耦合的,而是可以单独创建和读写,并在进程之间传递。

Channel

一个channel就是一个线程安全的队列——任何任务只要持有channel的引用,就可以向一端添加消息,也可以从另一端删除消息。使用channel发送消息时发送者并不知道谁是接收者,反之亦然。
从已关闭的空的channel中读出消息,将得到nil;向已经关闭的channel写入消息,该消息将默默地被弃用,向channel写入nil将发生错误。
channel的三种缓存区类型:
  • 阻塞型
  • 弃用新值型
  • 移出旧值型

go块

go块既可以写出事件驱动的代码来解决目前遇到的阻塞问题,又可以不牺牲代码的结构性和可读性。其原理是go块在底层将串行化代码透明的重写成了事件驱动的形式。
go块中的代码会被转换成一个状态机。当从channel中读出消息或者向channel中写入消息时,状态机将暂停,并释放它所占用的线程的控制权。当代码可以继续运行时,状态机进行一次状态转换,并可能在另一个线程中继续运行。

并发是一种心境

通过go宏的控制反转,ClojureScript可以让客户端编程在表面上具有多线程功能。这是协作式多任务的一种形式——一个任务不会强制打断另一个任务。

第七章 数据并行

OpenCL调优:大部分情况下,只需要让并行最大化、让工作项最小化,仅在必要的时候才会考虑进行优化。
要创建一个完整的程序,就需要将这个内核嵌入到主机程序中,步骤如下:
  • 创建上下文,内核和命令队列都将运行在这个上下文中
  • 编译内核
  • 创建输入数据的缓存区和输出数据的缓存区
  • 向命令队列中输入一个命令,让每一个工作项都运行一次内核程序
  • 获取结果
OpenCL适用于CPU:
  • 流式SIMD扩展指令集
  • 高级矢量扩展指令集,AVX
OpenCL平台模型:
工作项是在处理元件中执行的。在同一个计算单元中执行的工作项的集合称为工作组
内存模型:
  • 全局内存:同一个设备上执行的所有工作项都可以使用的内存
  • 常量内存:全局内存的一部分,在执行内核时保持不变
  • 局部内存:工作组私有的内存,可用于工作组中不同工作项之间的通信
  • 私有内存:工作项私有的内存

Lambda架构

MapReduce:
  • 指代一类算法:这类算法分为两个步骤,对一个数据结构首先进行映射操作,然后进行化简操作。
  • 指代一类系统:这类系统使用了上面的算法,将计算过程高效的分布到一个集群上。这类系统不仅可以将数据和数据处理分布到集群的多台计算机上,还可以在一台或者多台计算机崩溃时继续正常运转
Hadoop可以做到:
  • 将输入分配给多个mapper,每个mapper都会产生一些键值对
  • 这些键值对会被发送给reducer,产生最终的输出(通常也是一系列键值对)
  • 每个reducer对应的键是不同的,因此具有相同键的键值对都会发送给同一个reducer进行处理

批处理层

如果能够准确预测出未来会对原始数据进行怎样的查询,就可以预计算出一个批处理视图,这些视图包含衍生信息,也可以包含可以计算出衍生信息的数据。Lambda架构的批处理层就是用来计算这些批处理视图的

服务层

任务是对生成的批处理视图进行索引,并用来存放一个查询如何合并批处理视图的程序逻辑

加速层

加速层用来解决较高的延迟。有新数据产生时,一方面将其添加到原始数据中,这样批处理层可以进行处理。另一方面将其传给加速层,加速层会生成实时视图,实时视图和批处理视图合并来满足对最新数据的查询。
实时视图仅包含最后一次生成批处理视图后产生的原始数据所对应的衍生信息,当这部分数据被批处理层处理后,该实时视图将被弃用。
加速层可以是同步或者异步的,Storm是一种构建异步加速层的方法:
  • Storm实时的处理元组流。元组由spout创建、由bolt处理、由topology调度
  • spout和bolt都包含多个worker,这些worker并行执行,且分布在集群的多个节点上
  • Storm默认使用“至少会执行一次”的策略——bolt需要处理元组被重试的情况

© 李工 2021 - 2025