如何理解编译器?
编译器能帮助我们做哪些事情?
请基于macos或者linux发行版构架学习环境
- 06 | 编译器前端工具(一):用Antlr生成词法、语法分析器
- 07 | 编译器前端工具(二):用Antlr重构脚本语言 - 疯狂的AI
- 08 | 作用域和生存期:实现块作用域和函数 - 疯狂的AI
- 09 | 面向对象:实现数据和方法的封装 - 疯狂的AI
- 10 理解闭包原理 - 疯狂的AI
- 14 | 前端技术应用(一):如何透明地支持数据库分库分表? 点击进入详情
-
功能完备的语言包含了
- 编译器
- 运行时
- 内存管理
- 并发机制
- 解释器
- ...
- 标准库
- 算术计算
- 字符串处理
- 文件读写
- ...
-
如何设计一门计算机语言
- 清晰的需求
- 良好的设计
- 历史中的语言往往都是一个流行的系统的脚本
- 设计问题
- 案例
- js
- 解释执行的语言
- 嵌入到 HTML 中 ,方便下载和执行
- 跨平台
- 对编译速度有要求
- 词法分析
- 懒解析
- js
-
现代语言篇
- 各门语言的编译器的前端、中端和后端技术做对比和总结
- 对语言的运行时和标准库的实现技术做解析
- 对语言的运行时和标准库的宏观探讨
- 是垃圾收集机制
- 是并发模型
- 计算机语言设计上的 4 个高级话题
- 元编程技术
- 泛型编程技术
- 面向对象语言的实现机制
- 函数式编程语言的实现机制
-
前端编译技术
- 手写词法编译器
- 特点
- 实现较简单
- 便于做优化
- 便于处理特殊情况
- 例如
mysql
词法分析器处理字符集下某个字符串是否是合法的token
- 例如
- 特点
- 自顶向下分析为主的语法分析器
- 手写 vs 工具生成
- 差异化的语义分析功能
- 友好的语言特性
- 自动类型推导
- 自动类型推导可以减少编程时与类型声明有关的工作量
- C++ 11 中,采用了
auto
关键字做类型推导。 - Kotlin 中用
var
声明变量,也支持显式类型声明和类型推导两种方式 - Go 语言,会用“:=” 让编译器去做类型推导
- Java 语言也在 Java 10 版本加上了类型推导功能
- Null 安全性
- Null 引用其实是托尼·霍尔(Tony Hoare)在 1960 年代在设计某一门语言(ALGOL W)时引入的,后来也纷纷被其他语言所借鉴。
- 以 Kotlin 为例,在缺省情况下,它不允许你把 Null 赋给变量,因此这些变量就不需要检查是否为 Null。
- 安全调用(Safe Call)
- 采用“?.”操作符来访问
- Null 安全性在编译器里应该怎样实现呢
- 友好的语法糖
- 分号推断
- 单例对象
- 纯数据的类
- jdk14 支持 Record 类
- 没有原始类型,一切都是对象
- 一些友好的词法规则
- 嵌套的多行注释
- 标识符支持 Unicode
- 你可以用中文来声明变量和函数名称
- 多行字符串字面量
- 友好的语法糖
- 自动类型推导
- 手写词法编译器
-
中端编译技术 IR 与 SSA
- SSA
- 源代码中的一个变量,会变成多个版本,每次赋值都形成一个新版本
- 在
SSA
中,它们都叫做一个值(Value
) - 对变量的赋值就是对值的定义(
def
) - 定义出来之后 可以在定义其他值的时候被使用(
use
) - 形成 use-def 链
- 特点
- 定义未被使用--死代码删除
- 常数折叠,顺着链,替换值,形成常数传播
- 定义相同,值相同,可使用公共子表达式消除
Sea of Nodes
的特点总结- 特点
- 是把数据流图和控制流图合二为一,从而更容易实现全局优化
- 在生成图的过程中,顺带就可以完成很多优化了
- 从高到低的多层次 IR
- 对一个数组元素的访问
- 访问一个对象的成员变量
- 本地变量的访问
- 特点
- SSA
-
优化算法的总结
- 编译器基于IR的处理
- 层层地做 Lower
- 对 IR 做分析
- 实现各种优化算法
- 特点
- 有些基本的优化,是每个编译器都会去实现的
- 对于解释执行的语言,其编译器能做的优化是有限的
- Python 类型检查都是在运行期进行
- 根据不同的类型执行不同的功能
- 所有对象都是在堆中申请的
- JVM
- 编译器已做类型检查
- 简单优化 没消除无用代码
- 内联优化和逃逸分析没有做
- V8 的 Ignition 解释器
- 在寄存器利用方面比JAVA更有优势
- 但是动态类型拖了后腿
- Python 类型检查都是在运行期进行
- 动态类型语言
- 优化编译首要任务是类型推断
- 要为自己可能产生的推理错误做好准备,在必要的时候执行逆优化功能。
- Julia 也是动态类型的语言,但它采取了另一个编译策略。它会为一个函数不同的参数类型组合,编译生成对应的机器码。
- JIT 编译器可以充分利用推理性的优化机制
- 对于静态类型的语言,不同的编译器的优化程度也是不同的
- 编译器基于IR的处理
-
后端编译总结
- 关键算法
- 指令选择
- 模式树(Pattern Tree)
- 对一个 AST 生成指令,就是用这样的模式树或瓦片来覆盖整个 AST 的过程。所以,这样的算法也叫做基于模式匹配的指令生成算法。
#4
(Store_Offset)- 比如你在对象地址上加一个偏移量,就能获得成员变量的地址,并把数值保存到这个地址上
#9
(Lea) 相当于 x86 指令集中的 Lea 指令- 实现的算法
- 第一种
- 我们采取深度优先的后序遍历,也就是按照“左子节点 -> 右子节点 -> 父节点”的顺序遍历,针对每个节点去匹配上面的模式。
- 这种方法,是自底向上的做树的重写。
- 第二种
- 类似
Graal
编译器所采用的方法,自顶向下的做模式匹配 - 是找出用模式匹配来覆盖 AST 的所有可能的模式,并找出其中 Cost 最低的
- 类似
- 第三种
- BURS 算法
- 第一种
- 寄存器分配
- 线性扫描算法并不能获得寄存器分配的最优解
- 线性扫描算法可以采用一些策略,让一些使用频率低的变量被溢出,而像高频使用的循环中的变量,就保留在寄存器里
- 还有一些其他提升策略。比如,当存在多余的物理寄存器以后,还可以把之前已经溢出的变量重新复活到寄存器里。
- 思路
- 线性扫描整个代码,并给活跃变量分配寄存器。如果物理寄存器不足,那么就选择一个变量,溢出到内存中
- 指令排序
- 指令选择
- 关键算法
-
编译器后端与语言的设计 -因素
- 平衡编译速度和优化效果
- 确定所支持的硬件平台
- 设计后端
DSL
-
运行时
- 一门语言的构成
- 编译器
- 运行时
- 每种语言都有特定的执行模型
- 执行模型需要运行时系统(Runtime System)的支持
- 简称为 “运行时”
- 执行模型需要运行时系统(Runtime System)的支持
- 包含的主要功能
- 程序运行机制
- 内存管理机制
- 并发机制
- java 运行时
- JVM 规定了一套程序的运行机制
- 定义了一套字节码来运行程序
- 规定了一套类型系统,包括基础数据类型、数组、引用类型等
- 定义了
class
文件的结构 - 提供了一个基于栈的解释器,来解释执行字节码
- JVM 还支持即时编译成机器码并执行的机制
- Java 程序之间的互相调用,需要遵循一定的调用约定或二进制标准,包括如何传参数等等
- JVM 对内存做了统一的管理
- 把内存划分为程序计数器、虚拟机栈、堆、方法区、运行时常量池和本地方法栈等不同的区域
- JVM 封装了操作系统的线程模型,为应用程序提供了并发处理的机制
- JVM 实际上提供了一个基础的对象模型,
JVM
上的各种语言必须遵守 - 基于
JVM
的语言程序要去调用 C 语言等生成的机器码的库,会比较难。 - 在内存管理上,程序不能直接访问内存地址,也不能手动释放内存
- 在并发方面,
JVM
只提供了线程机制。
- JVM 实际上提供了一个基础的对象模型,
- JVM 规定了一套程序的运行机制
- python 运行时
- Python 也提供了一套字节码,以及运行该字节码的解释器
- 字节码中操作的那些标识符,都是 Python 的对象引用。
- 在内存管理方面,Python 也提供了自己的机制,包括对栈和堆的管理
- Python 运行程序的时候,有些时候是运行机器码,比如内置函数,而有些时候是解释执行字节码。
- 栈帧跟 C 语言程序的栈帧是没啥区别的
- 解释器本身主要就是一个 C 语言实现的函数,而操作数栈就是这个函数里用到的本地变量。 因此操作数栈也会像其他本地变量一样,被优化成尽量使用物理寄存器,从而提高运行效率。
- 栈桢中的操作数栈,其实是有可能基于物理寄存器的。
- Python 还提供了对堆的管理机制。
- 通过 Python 提供的一个 Arena 机制,使得内存的申请和释放更加高效、灵活
- Python 还提供了基于引用的垃圾收集机制
Python
把操作系统的线程进行了封装. 让Python
程序能支持基于线程的并发。同时,它也实现了协程机制
- C、C++、Go 的运行时
- C 语言最主要的运行时,实际上就是操作系统
- 深入使用 C 语言,某种意义上就是要深入了解操作系统的运行机制。
- C 没有自动内存管理机制
- C 可以直接使用线程机制 但是操作系统没有提供协程和Actor机制,C 没有这个机制
- 不过有一个程序 crt0.o,有时被称作是 C 语言的运行时
- Go 语言虽然也是编译成二进制的可执行文件,但它的运行时要复杂得多
- Go 语言最显著的特点是提供了自己的并发机制,也就是 goroutine。
- 在 Android 平台上,你可以把 Java 程序以 AOT 的方式编译成可执行文件
- C 语言最主要的运行时,实际上就是操作系统
- 每种语言都有特定的执行模型
- 标准库
- 库和标准库
- 根据库的使用场景和与编译器的关系
- 标准库
- 标准库,供用户的程序调用。
- 运行时库
- 运行时库,它们不是由用户直接调用的,而是运行时的组成部分。
- 内置函数
- Built-in 或者 Intrincics 的内置函数,它们是用来辅助生成机器码的。
- 库和标准库
- 标准库的特殊性
- 有的库可以用本语言来实现,而有的库必须要用其他语言来实现
- 第二,标准库的接口不可以经常变化,甚至是要保持一直不变
- ,标准库往往集中体现了一门语言的核心特点。
- 标准库需要包含什么功能?
- 包含 IO 功能,包括文件 IO、网络 IO
- 终端本身就相当于一个文件,这实际上是用了文件 IO 功能。
- 支持内置的数据类型。
- 支持各种容器型的数据结构。
- 对日期、图形界面等各种不同的功能支持
- 包含 IO 功能,包括文件 IO、网络 IO
-
垃圾收集算法
- 常见的垃圾收集算法
-
mark and sweep - 标记-清除
- 从GC根节点出发,顺着对象的引用关系,依次标记可达的对象
- GC根节点
- 全局变量
- 常量
- 栈内的本地变量
- 寄存器中的本地变量
- GC根节点
- 剩下的对象,就是内存垃圾
- 从GC根节点出发,顺着对象的引用关系,依次标记可达的对象
-
mark and compact - 标记-整理
- 标记-清除 算法运行时间长了之后,会形成内存碎片
- 申请任意大于碎片最大值的内存空间都会不足
- 使用标记-整理算法可以将存活的对象整理至一边,消除掉内存碎片
-
stop and copy - 停止-拷贝
- 将内存分为新旧两个空间
- 堆指针指向自由空间的开始位置
- 申请空间时,指针向右移动即可
-
引用计数
- 在对象中保存该对象被引用的数量,一旦这个引用数为零,那么可以作为垃圾被收集
- 自动引用计数 ARC
-优点 - 不需要为了垃圾收集而专门停下程序 -缺点 - 不能处理循环引用(Reference Cycles)的情况
-
- 内存垃圾
- 保存在堆里的,无法从程序访问的对象
- 所有不可达的内存为内存垃圾
- 分代收集
- 新创建的对象往往会很快死去
- 使用临时变量指向一些新创建的对象,这些对象大多数在退出方法时,就没用了
- 新生代频繁回收
- 适合复合式收集算法
- 老一代生命周期较长
- 适合标记-清除或标记-整理算法
- 增量收集、并发收集
- 增量收集每次只完成部分收集工作,没必要一次把活干完,从而减少停顿。
- 并发收集
- 不影响程序执行的情况下,并发执行
GC
- 不影响程序执行的情况下,并发执行
- 常见的垃圾收集算法
-
python与引用计数算法
- 使用相同语言的算法的语言有
- Swift
- 方舟编译器
- 内存管理和
GC
机制- 每个数据都是对象
- 对象申请于堆
- c、java 本地变量申请于栈
- python申请的对象有个固定的开销
- 栈的优势
- 不会产生内存碎片
- 数据局部性好
- 数据收放自如
- 堆的劣势
- 太多的小对象
- 太多系统调用
- 性能消耗大
- 易产生内存碎片
- 数据局部性差
- 基于区域(Region-based)的内存管理方法
- Python 每次都申请一大块内存,这一大块内存叫做 Arena
- 需要较小内存时,直接从 Arena 上划拨
- 需要归还内存的时候,不一定直接归还给操作系统,而是归还给 Arena
- 每个
PyObject
都有一个obj_refcnt
- 解释器执行字节码的时候,会根据不同的指令自动增加或者减少 ob_refcnt 的值
obj_refcnt
值为0时,可清除
- 其他语言的垃圾收集
- 采用像停止 - 拷贝这样的算法,其实是用空间换时间,以更大的内存消耗换来性能的提升。
- 这其实也是为什么 Android 手机比 iPhone 更加消耗内存的原因之一。
- 使用相同语言的算法的语言有
-
为什么在垃圾收集时,要停下整个程序?
- 采用标记 - 清除算法时,你就必须要停下程序
- 你必须从所有的 GC 根出发,去找到所有存活的对象,剩下的才是垃圾
- 你不仅要停下当前的线程,扫描栈里的所有 GC 根,你还要停下其他的线程
- 如果垃圾收集算法需要做内存的整理或拷贝,那么这个时候仍然要停下程序
-
如何能减少停顿时间?
- 第一招,分代收集可以减少垃圾收集的工作量
- 第二招,可以尝试增量收集。
- 三色标记法
- 特点
- 黑色对象永远不能指向白色对象
- 色标记法中,黑色的节点是已经处理完毕的,灰色的节点是正在处理的。如果灰色节点都处理完,剩下的白色节点就是垃圾。
- 特点
- 编译器做什么配合?
- 编译器需要往生成的目标代码中插入读屏障(Read Barrier)和写屏障(Write Barrier)的代码
- 第三招:并发收集。
- 除了少量的时候需要停下整个程序(比如一开头处理所有的 GC 根),其他时候是可以并发的,这样就进一步减少了总的停顿时间。
-
并发中编译技术
- 并发需求增长的因素
- CPU 在制程上的挑战越来越大
- 现代应用对并发处理的需求越来越高
- 并发技术
- 线程
- 协程
- Actor 模式
- 并发的底层机制:并行与并发、进程与线程
- 超线程(Hyper Threading)技术
- 让一个内核可以同时执行两个线程,增加对 CPU 内部功能单元的利用率
- 操作系统会用分时技术,让一个程序执行一段时间,停下来,再让另一个程序运行
- 由操作系统的一个调度程序(Scheduler)来实现的
- 以进程为单位来做调度,开销比较大。
- 切换进程的时候,要保存当前进程的上下文,加载下一个进程的上下文,也会有一定的开销。
- 用户级上下文
- 寄存器上下文
- 系统及上下文
- 由操作系统的一个调度程序(Scheduler)来实现的
- 线程技术
- 一个进程内部,可以有多个线程,每个线程都共享进程的资源
- 内存资源
- 操作系统资源
- 安全属性
- 栈和寄存器资源
- 一个进程内部,可以有多个线程,每个线程都共享进程的资源
- 线程调度原理
- 操作系统会让一个线程运行一段时间,然后把它停下来,把它所使用的寄存器保存起来,接着让另一个线程运行
- 把进程作为资源分配的基本单元,而把线程作为并发执行的基本单元
- 进程作为并发基础
- google chrome
- 每打开一个 Tab 页,就新启动一个进程
- 浏览器中多个进程之间不需要有互动
- 各个进程所使用的资源是独立的
- 一个进程崩溃也不会影响到另一个
- google chrome
- 线程作为并发基础
- 轻量级,消耗的资源比较少
- 在一般的网络编程模型中,我们可以针对每个网络连接,都启动一条线程来处理该网络连接上的请求
- 采用线程模型的话,程序就可以在不同线程之间共享数据
- SQL 案例
- 一个客户端提交了一条SQL之后,查询结果可以缓存起来。
- 如果另一个用户恰好也执行了同一个SQL,那么可以直接利用缓存
- SQL 案例
- 共享内存也会带来一些问题
- 多个线程访问同样的数据的时候,会出现数据处理的错误。如果使用并发程序会造成错误
- Java 语言内置的并发模型就是线程模型
- 进程作为并发基础
- 超线程(Hyper Threading)技术
- 并发需求增长的因素
-
Java 的并发机制
- 语言层面对并发编程的支持
Thread
类Runnable
实现类- 相关的关键字
synchronized
volatile
- 它解决的是变量的可见性问题
- 线程1写入线程2读取不到问题
- 因为CPU的高速缓存,线程1写入不会立即回写到内存
- volatile
- 让程序在访问被修饰的变量的内存时,让其他处理器能够见到该变量最新的值
- 当某个线程写数据的时候,要写回到内存,而不仅仅是写到高速缓存;当读数据的时候,要从内存中读,而不能从高速缓存读。
- 内存屏障(Memory Barriers)
- LoadLoad 屏障、StoreStore 屏障、LoadStore 屏障和 StoreLoad 屏障
- 养成直接看字节码、汇编码来研究底层机制的习惯,
- 两个特殊的指令
monitorenter
- 试图获取某个对象引用的监视器(monitor)的所有权
monitorexit
- 我们用了锁的机制,保证被保护的代码块在同一时刻只能被一个线程访问,从而保证了相关操作的原子性。
- cmpxchg 指令
- 通过一条指令,完成比较和交换的操作
- 作用
- 通过这样一条指令,计算机就能支持原子操作。
- 实际执行的时候,r10 中的值并不是简单的 0 和 1,而是获取了 Java 对象的对象头,并设置了其中与锁有关的标志位。
lock
前缀- 让这条指令在同一时间,只能有一个内核去执行。
- cmpxchg 指令
-
协程的特点和使用场景
- 1958 马尔文 · 康威(Melvin Conway)提出
- 20 世纪 60 年代高德纳(Donald Ervin Knuth)总结为两种子过程(Subroutine)的模式之一。
- 函数调用
- 协程
- 一般是程序交出运行权,之后又被另外的程序唤起继续执行
- 线程是通过操作系统的调度器来控制的
- 在 JVM 中,缺省会为每个线程分配 1MB 的内存,用于线程栈
- 别名
- 绿色线程、纤程
- 共同点
- 协程占用的资源非常少
- 协程是用户自己的程序所控制的并发。
- 协程和函数调用的区别
- 使用协程跟我们平常使用函数几乎没啥差别
- 函数调用时,调用者跟被调用者之间像是一种上下级的关系
- 在协程中,调用者跟被调用者更像是互相协作的关系
- 例如 一个生产者 一个消费者
- 使用场景
- 生产者和消费者模式 生成器 场景
- 消息队列编程 多线程
- Unix 管道 多线程
- 协作关系
- 词法分析器
- 语法分析器
- 语法分析器消费1个 词法分析器 生产一个
- IO 密集型的应用
- 协程在发起请求之后就把控制权交出,调度程序接收到数据之后再重新激活协程,这样就能高效地完成 IO 操作
- 微服务架构的应用
- 近年来异步编程模型
- Node.js
- 网络通讯等 IO 操作不必阻塞线程,而是通过回调来让主程序继续执行后续的逻辑。
- 易产生回调地狱
- 协程可以让你用自己熟悉的命令式编程的风格,来编写异步的程序
- 协程用于同步和异步编程的时候,其调度机制是不同的
- 异步调用
- 把异步 IO 机制与协程调度机制关联起来
- 生产者和消费者模式 生成器 场景
- 协程的运行原理
- 函数是调用的栈帧
- 协程
- 程序可以从堆里申请一块内存,保存协程的活动记录,包括本地变量的值、程序计数器的值(当前执行位置)等等
- 把活动记录保存到堆里 类似于闭包函数
- 闭包函数
- 也需要在堆里保存闭包中的自由变量的信息,并且在下一次调用的时候,从堆里恢复
- 闭包不需要保存本地变量,只保存自由变量就行了;也不需要保存程序计数器的值,因为再一次调用闭包函数的时候,还是从头执行
- 协程则是接着执行
yield
之后的语句。
- 协程的调度,包括协程信息的保存与恢复、指令的跳转,需要编译器的帮忙吗
- 对于C和C++
- 可以用
setjmp
、longjmp
等函数 - 用库实现,通常要由程序管理哪些状态信息需要被保存下来
- 可能要专门设计一个类型,来参与实现协程状态信息的维护。
- 可以用
- 编译器帮忙
- 自动确定需要保存的协程的状态信息,并确定需要申请的内存大小
- 一个协程和函数的区别,就仅仅在于是否使用了 yield 和 co_return 语句而已
- 对于C和C++
Stackful
和Stackless
的协程- 协程和函数的相似性
- 主程序调用,执行完成后控制权给主程序
- 使用栈来管理本地变量和参数信息
- 栈未完全运行完毕之后,使用堆保存活动记录
- 协程中可调用其他函数
- 如何解决两级或多级调用的问题?
- 协程的逐级调用过程,形成了自己的调用栈,这个调用栈需要作为一个整体来使用,不能拆成一个个单独的活动记录
- 添加一个辅助栈
Side Stack
- 需要一个辅助的栈来运行协程的机制,叫做
Stackful Coroutine
- 在主栈上运行协程的机制,叫做
Stackless Coroutine
Stackless
- 只能在顶层的函数里把控制权交回给调用者
Stackless
的协程用的是主线程的栈,也就是说它基本上会被绑定在创建它的线程上了Stackless
的协程的生命周期受制于他们的创建者生命周期
Stackful
- 可以在协程栈的任意一级,暂停协程的运行。
Stackful
的协程,可以从一个线程脱离,附加到另一个线程上- 生命周期可以超过创建者的生命周期
- 不同语言的协程实现差异
- c++
- C++20 标准中,增加了协程特性。
- 采用了微软的
Stackless
模式。 - 关键字
- co_await
- co_yield
- co_return
- 库实现
- 腾讯的微信团队就开源了一套协程库,叫做
libco
libco
发明了共享栈的机制- 用同步的编程风格来实现异步的功
- 腾讯的微信团队就开源了一套协程库,叫做
- python
- generator
- 3.4 后 使用 async await
- java
- Java 原生是不支持协程的
- 几种方法可以让其支持
- 虚拟机补丁
- 字节码操纵,从而改变 Java 缺省的控制流执行方式,并保存协程的活动记录。
- 基于 JNI。
- 把线程封装成协程
- javascript
- es6 generator
- es7 async await
- julia 、 go
- 对称的协程机制
- 多个协程可以通过 channel 通讯,null或Chanel已满自动停止
- Goroutine 是 Stackful 还是 Stackless?
- Stackful
- 优点
- 生命周期可超过创建者
- 可在线程间转移
- 缺点
- 预分配较多的内存用作协程的栈空间
go routine
2klibco
128 k- 区分
go routine
有编译器的支持- 编译器可以为每个函数生成这样的序曲代码
- 预分配较多的内存用作协程的栈空间
channel
机制- 在同一个时刻,只有一个
Goroutine
能够读写channel
channel
在底层也采用了锁的机制
- 在同一个时刻,只有一个
- 协程的调度时机
- 对于 generator 类型的协程,基本上是同步调度的,
- 第二个调度机制,是跟异步 IO 机制配合
- 像线程那样的抢占式(preemptive)的调度
- 由于有编译器的参与,这种类似抢占的逻辑是可以实现的
- 协程与异步 IO 结合是一个趋势。
- c++
- 协程和函数的相似性
-
什么是 Actor 模型?
- Actor 模型是 1973 年由 Carl Hewitt 提出的
- Actor 模型中,并发的程序之间是不共享内存的
- 通过互相发消息来实现协作
- 每个 Actor 都有一个邮箱,用来接收其他 Actor 发来的消息
- 发完即回,异步交互
- 如果 a - > b , b 需要返回消息,那么 b 也是通过发送消息的方式
- 不共享内存,能解决传统上需要对资源做竞争性访问的需求吗?
- 电影院卖票功能
- 线程或协程,会用加锁的方式实现同步互斥,但多个程序是串行的,因此系统性能差
- Actor 模式
- 由于 Actor 之间不共享任何数据,因此不仅增加了数据复制的时间,还增加了内存占用量
- 基于消息的并发机制,基本上是采用异步的编程模式
- 支持 Actor 的最有名的语言是 Erlang。
- 特点
- 对并发的支持非常好,所以它也被叫做面向并发的编程语言(COP)
- 用 Erlang 可以编写高可靠性的软件
- 模式
Erlang
的软件由很多Actor
构成;Actor
可以分布在多台机器上- 一个
Actor
甚至机器出现故障,都不影响整体系统,可以在其他机器上重新启动该 Actor;
- 每个
Actor
叫作一个进程(Process
)Process
:Erlang
运行时的并发调度单位。Actor
的代码可以在运行时更新
- 就像生命体与细胞的关系
- 案例
- 消息队列系统
RabbitMQ
- 分布式的文档数据库系统
CouchDB
- 消息队列系统
- 特点
- 电影院卖票功能
引用内容版权归 【极客时间】 所有