2016年的 elixir 大会上, Jose 提到了一个重要的项目 “GenStage”. 并声明它将取代 elixir 中原有的标准库 GenEvent
. 那么, GenStage 到底有什么用处, 它比 GenEvent 又优越在哪里呢?
首先, 我们要了解一下什么是 GenEvent.
GenEvent
GenEvent 是一个行为(behaviour)模块, 我们之前提到的 GenServer, Supervisor, 都是行为模块. 也就是说, 我们在自己的模块定义中加入 use GenEvent
, 就代表该模块使用了 GenEvent 的模板, 里面包含了所有定义 GenEvent 所必需的函数.
GenEvent 的作用是实现函数化的事件处理, 一个事件处理模型是由一个通用事件管理进程和多个可以动态增删的事件句柄(handlers)**组成. 听起来是不是很像 Supervisor, 不同的地方在于 Supervisor 管理的是进程, 而 GenEvent 管理的是句柄. 单个事件处理模型只有一个进程, 好处就是我们不需要为一些持续时间很短的任务新建进程, 比如说”记录一条错误日志”, 坏处就是必须要等一个事件处理完了, 才能处理下一个. 如果有一个事件的处理进入循环, 那么其它所有事件都没法处理了.
例子
比如说一个日志记录系统, 对于日志信息可以有不同的处理方法. 一个句柄可以将错误信息打印到终端, 另一个可以写入文件, 还有句柄可以将信息保存在内存里直到信息被阅读.
让我们来实现一个类似信箱的功能:
# 定义一个事件句柄
defmodule LoggerHandler do
use GenEvent
# 回调
def handle_event({:log, x}, messages) do
{:ok, [x | messages]}
end
def handle_call(:messages, messages) do
{:ok, Enum.reverse(messages), []}
end
end
# 新建一个事件管理器
{:ok, pid} = GenEvent.start_link([])
# 将事件句柄添加到事件管理器..
GenEvent.add_handler(pid, LoggerHandler, [])
#=> :ok
# 向事件管理器发送事件.
GenEvent.notify(pid, {:log, 1})
#=> :ok
GenEvent.notify(pid, {:log, 2})
#=> :ok
# 用 call 函数调用特定句柄..
GenEvent.call(pid, LoggerHandler, :messages)
#=> [1, 2]
GenEvent.call(pid, LoggerHandler, :messages)
#=> []
在这里, GenEvent起到了一个工人的作用, GenEvent.notify(pid, {:log, 1})
可以理解为:我们对工人说: “你好, 给你{:log, 1}” 工人心想, 什么意思, 不懂啊, 就去看之前的句柄登记手册, 哦, 之前有个叫 LoggerHandler
的句柄在这里登记过了, 他告诉我, 如果有人发送{:log, x}
过来, 就把x 存到信箱里. 于是工人把 1
存入, 并回了句:ok
. 至于GenEvent.call(pid, LoggerHandler, :messages)
, 相当于我们明确地告知工人, “按 LoggerHandler 说的做.” , 工人就不会再去查登记手册了.
至于GenStage, 简单地说, 它允许我们雇佣多个工人来同时处理. 具体的原理, 我们下回再讲.
GenEvent 里的主要函数
ack_notify((manager, event)
发送一个提醒给事件管理器, 等到该事件开始被处理时, 返回: ok
.
add_handler(manager, handler, args)
添加一个新的事件句柄到事件管理器.
add_mon_handler(manager, handler, args)
添加一个被监控的句柄到事件管理器.
call(manager, handler, request, timeout \ 5000)
对 manager 里注册的某个 handler 发送一个同步的 call 请求.
notify(manager, event)
发送一个事件通知到 manager.
remove_handler(manager, handler, args)
从 manager 从删除一个事件句柄
start(options \ [])
启动一个没有链接的事件管理进程(在监督树之外)
start_link(options \ [])
启动一个链接到当前进程的事件管理器.
stop(manager, reason \ :normal, timeout \ :infinity)
以给定的原因停止 manager.
stream(manager, options \ [])
从 manager 返回一个会消耗事件的流
swap_handler(manager, handler1, args1, handler2, args2)
用新的事件句柄交换掉旧的.
swap_mon_handler(manager, handler1, args1, handler2, args2)
用新的被监控的事件句柄换掉旧的.
sync_notify(manager, event)
发送一个同步事件提醒到 manager, 在其被事件管理器的每个句柄调用了之后返回: ok
which_handlers(manager)
返回 manager 里所有句柄
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。