第五章 Actor
5.1 更加面向对象
函数式编程不使用可变状态,也就避免了共享可变状态带来的问题。相比之下,使用actor模型保留了可变状态,只是不进行共享。
Elixir是Erlang的虚拟机。actor在Elixir中被称为进程,是个轻量级的概念,比操作系统中的线程还要轻。
5.2 第一天:消息和信箱
第一个actor
先来尝试创建一个简单的actor,并向其发送一些消息。我们将创建一个叫talker的actor,其收到不同的消息时会输出不同的结果。所发送的消息是一个元组(tuple)。
def module Talker do
def loop do
recevie do
[:greet, name] -> IO.puts("Hello ${name}")
[:praise, name]->IO.puts("#{name}, you're amazing")
[:celebrate, name, age]->IO.puts("Here's to another #{age} years, #{name}")
end
loop
end
end
pid = spawn(8Talker.loop/0)
send(pid, [:greet, "World"])
send(pid, ["praise", "Dewey"])
send(pid, [:celebrate, "Louie", 16])
sleep(1000)
spawn创建actor实例,并获得进程标识符。
队列式信箱
消息并不是直接发送到一个actor,而是发送到一个信箱。发送消息时不会被阻塞。actor按照信箱接收消息的顺序来一次处理消息,且仅在当前消息处理完成后才会处理下一个消息,因此我们只需要关心发消息时的并发问题即可。
接收消息
通常actor会进行无限循环,通过recevice等待接收的消息,看上面的代码中该函数通过递归调用实现无限循环,那么可能有人会问这样不会造成栈溢出吗?与许多函数式语言一样,Elixir实现了尾调用的消除。为调用消除指的是如果函数在最后调用了自己,那么递归调用将被替换成一个简单的跳转。
为了彻底关闭一个actor,需要满足两个条件。一个是需要告诉actor在完成消息处理之后关闭;第二个是需要知道actor合适完成关闭。
def module Talker do
def loop do
recevie do
[:shutdown] -> exit(:normal)
end
loop
end
end
send(pid, [:shutdown])
有状态的actor
双向通信
actor模型没有提供直接回复消息的机制,但我们可以自行解决:将发送进程的标识符包含在消息中。通过这个机制,消息的接受者可以回复消息。
5.3 第二天:错误处理和容错性
并发很重要的一个特性是并发代码具有容错性。
在大多数语言中,唯一的处理方法是添加一些检查参数的代码,当检查到非法参数时报错。Elixir提供了另外一种方法--将错误处理隔离到一个管理进程中,这个方法看似简单,确是一个很大的改进,使代码更简洁、更具维护性,也更可靠。
错误检测
任其崩溃
防御式编程主要通过预言可能出现的缺陷来实现容错。使用actor模型的程序并不进行防御式编程。而是遵循了任其崩溃的哲学。
第六章 通信顺序进程
6.1 万物皆通信
通讯顺序进程(Communicating Sequential Process, CSP)模型,与actor类型,也是由独立的,并发的执行的实体所组成,实体之间也是通过发送消息进行通信。但两种模型的最重要差别是:CSP模型不关注发送消息的实体,而是关注发送消息时使用的channel。
数据并行
7.1 隐藏在笔记本电脑中的超级计算机
图形处理单元GPU,是一个强力的数据并行处理器,其用于数学计算时性能超过CPU,这种做法成为基于图形处理器的通用计算(GPGPU编程)。
7.2 第一天:GPGPU编程
图形处理与数据并行
3D游戏的一个场景是由无数个小三角形构成的,每一个三角形都需要根据与视点相关的透视关系计算出其在屏幕上的位置,并进行裁剪,处理光照,修改纹理等,这些操作每秒钟需要进行25次以上。
虽然需要处理的数据量是巨大的,但其有一个非常好的特性:施加在数据上的操作都是相对简单的向量操作或矩阵操作。因此这种场景非常适合数据并行。
数据并行可以通过多种方法来实现,我们要学习其中的两种:流水线和对ALU。
网友评论