为gen_event辩护

gen_event曾一度引起Erlang和Elixir开发人员的不满。他们已经撰写了许多文章,并就gen_event的各种替代办法举行了一次以上的会议。在这篇文章中,我要说明的是,尽管gen_event存在缺陷,但它仍然是一个非常有用的模块。我也认为它的核心设计最终要比许多人认为的灵活得多。

某些设计决策会产生灵活的系统。例如,Erlang的异步消息发送和带有超时的同步消息接收为开发人员提供了全面的消息传递行为。如果要同步消息的发送,最好带上一个唯一(reference)引用来发送消息,然后执行receive语句,对这个引用进行接收模式匹配。这基本上就是使用gen_*:call这一类函数调用时内部的实际情况。如果要异步接收消息,则可以使用带有 after 0 的receive语句,在没有匹配的消息的情况下,立即超时。下面是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
sync_send(Pid, Msg) ->
Ref = make_ref(),
Pid ! {Ref, Msg},
receive
{Ref, Reply} -> Reply
end.

async_recv() ->
receive
M -> {ok, M}
after
0 -> none
end.

问题:如果Erlang给提供你同步发送和异步接收,你将如何去实现异步发送和同步接收?

Erlang消息传递的核心设计足够灵活,可以表达默认情况下未实现的消息传递语义。 当我们将注意力转向gen_event时,请记住这一点。

对于那些不熟悉gen_event的人来说,它的工作方式或多或少可能像这样:gen_event:start_link启动所谓的事件管理器进程。 当你编写一个实现gen_event行为的模块时,你正在编写所谓的事件处理程序。 事件管理器有一个已安装事件处理程序的列表,每个事件处理程序都有自己的状态。 当进程调用gen_event:notify时,事件管理器将一次调用一个其安装的处理程序(毕竟,事件管理器只是一个进程)。 我会重复重点:事件管理器进程一次执行一个所安装的事件处理程序。 这个设计引发了一些争议,特别是José Valim,他有一篇关于如何用监督者(作为事件管理器)和一堆gen_server(作为事件处理程序)替换gen_event的博客文章

我知道我有点怪,但是我认为gen_event的默认行为非常好,并且最终比José建议的并发处理程序的解决方案灵活得多。如果希望并发处理事件,则可以通过安装事件处理程序(将消息转发到现有进程或派生进程以执行事件处理代码)来轻松实现事件的扇出。但是,如果事件的并发处理是默认行为,你将如何实现事件的顺序处理呢?我怀疑你最终会实现一个效率较低的gen_event版本;因为你将发送不必要的消息,这导致效率较低。

问题:如果gen_event在默认情况下并发运行事件处理程序,那么你将如何依次运行处理程序呢?

总之,gen_event很像Erlang的消息传递,它提供了一个灵活的基础,你可以轻松地实现不同的行为。 我很高兴gen_event的存在,并且它被设计为顺序处理事件是默认行为。

原文链接: http://www.rkallos.com/blog/2018/05/22/in-defense-of-gen-event/