为了让几十个任务能同时执行, Erlang 采用了 Actor 模型, 每个 actor 都是虚拟机中的一个独立进程. 在 Erlang 中, 并发的基本单位是进程(Actor). 每个进程代表一个持续的活动, 是某段程序代码的执行代理, 与其他按各自的节奏执行自身代码的进程一起并发运行, 进程之间靠消息来通信. Erlang 的并发是很廉价的, 派生一个进程就跟在 OOP 中分配一个对象的开销差不多. 更形象一些, 如果你是 Erlang 世界中的一个 actor, 你将会是一个孤独的人, 独自坐在一个没有窗户的黑屋子里, 在你的邮箱旁等待着消息. 当你收到一条消息时, 会用特定的方式来响应这条消息:收到账单就要进行支付;收到生日卡, 就回一封感谢信;对于不理解的消息, 就完全忽略. 人和人之间只能通过写信进行交流, 就是这样.
理解进程之前先要说的是 pid 这个特殊的 Erlang 数据类型, Erlang 支持进程编程, 任何代码都需要一个进程作为载体才能执行, 每个进程都有一个唯一标识符, 也即是 pid. 在 erlang shell 中, 进程的 pid 会以类似<0.62.0>的格式打印, 这个格式只用于调试比较目的.
self()
会告诉你当前进程(也就是调用 self() 的那个进程)的 pid.
PROCESS OPERATIONS
派生进程
派生进程的函数有两个: spawn/1
, spawn/3
. 第一个仅有一个参数, 就是用作新进程入口的 fun 函数, 这里要注意的是, 我们无法得到函数F的返回值. 我们只能得到它的 pid, 因为进程不会返回任何东西; 另一个则需要模块名, 函数名, 参数列表三个参数.
|
|
消息传递
Erlang 的进程之间可以互相使用 !
运算符发送消息.
该操作符的左边是一个 pid, 右边可以是任意 Erlang 数据项. 这个数据项会被发送给左边的 pid 所代表的进程, 这个进程就可以访问它了.
|
|
发送一些无人会看的消息的用处就和写一些表达自我情绪的诗一样(换句话说, 就是不大有用), 呵呵哒.
因此, 我们还需要 receive 表达式来接收消息.
receive 的语法形式如下:
|
|
举个例子:
|
|
|
|
函数执行到了 receive 表达式. 因为进程邮箱为空, 所以海豚进程会一直处于等待消息状态.
收到了消息"oh, hello dolphin!”. 函数会先对 do_a_flip 进行模式匹配. 失败了. 然后再尝试模式fish, 也失败了. 最后遇到了匹配一切的子句(_), 匹配成功.
进程打印消息"Heh, we’re smarter than you humans.”, 接着, 进程也随之结束. 因此, 再次发送 fish
给 Dolphin
的时候, 就不会对该消息做任何反应.
超时接收
上面提到的, 当进程执行到 receive 表达式以后会一直处于等待消息的状态, 而且如果不做处理就会一直等下去. 出现这样情况的原因有很多, 比如准备发送消息的进程在消息发出之前就崩溃掉了. 为了避免超时问题的出现, 可以在 receive 增加超时设置, 如下:
|
|
等待 Delay 毫秒以后如果没有 Match 的消息就会执行 after 中的内容, 在这里就是 Expression2
注册进程
每个 Erlang 系统都有一个本地进程注册表用于注册进程的简单命名服务. 一个名称一次只能用于一个进程.
可以使用函数 erlang:register(Name, Pid) 为进程命名. 如果进程死亡了, 它会自动失去自己的名字. 也可以使用函数unregister/1手工解除进程的名字注册. 可以调用 registered/0
得到所有已注册进程的列表, 或者通过shell命令 regs()
得到更详细的信息. 使用内置函数 whereis/1
可以查找当前与指定注册名对应的 pid.
假设某个进程崩溃了, 对应的服务被重启, 新的服务进程 pid 将会改变, 此时无需逐个通知给系统中的所有进程, 只要更新进程注册表就可以了.
|
|
链接 Link
链接(link)是两个进程之间的一种特殊关系. 当在两个进程间建立了这种关系后, 如果其中一个进程由于意外的抛出, 出错或者退出而死亡时, 另外一个进程也会死亡, 把这两个进程独立的生存期绑定成一个关联在一起的生存期.
Erlang 中有一个原生函数 link/1
, 用于在两个进程间建立一条链接, 它的参数是进程的pid. 当调用它时, 会在当前进程和参数pid标识的进程之间建立一条链接. 要去除链接, 可以使用 unlink/1
.
当链接进程中的一个死亡时, 会发送一条特殊的消息, 其中含有死亡原因相关的信息. 如果进程正常死亡了(函数正常执行完毕), 就不会发送这条消息.
注意, link(spawn(Function))
或者 link(spawn(M,F,A))
并不是一个原子操作. 有时, 进程会在链接建立成功之前死亡, 从而导致不期望的行为. 因此, Erlang 中增加了 spawn_link/1
和spawn_link/3
函数, 对应 spawn/1
和 spawn/3
, 创建一个进程, 并和它建立链接, 这个操作是原子级的, 也就意味着要么成功, 要么失败.
|
|
监控器 Monitor
有时候链接可能不是你想要的, 也许只是想知道目标进程挂了而不想牵连着一起被杀死, 那么这时候就要用监控器(Monitor). 监控器其实是一个单向的链接, 可以让一个进程在不影响目标进程的情况下对目标进程进行监视.
创建监控器的函数是 erlang:monitor/2
, 它的第一个参数永远是原子 process, 第二个参数是进程的 Pid.
|
|
每当被监控的进程死亡时, 监控进程都会收到一条消息, 格式为 {'DOWN', MonitorReference, process, Pid, Reason}
其中, MonitorReference
可以用来解除对一个进程的监控. 记住, 监控器是可叠加的, 因此会收到多条 DOWN 消息. 引用可以唯一确定一条 DOWN 消息. 类似 spawn_link
的也有原子级的 spawn_monitor