erlang观察者模型gen_event
gen_event 和面向对象当中的 观察者有异曲同工之妙,本质上是将一个事件广播给N个观察者
1. 工作原理
step1.使用 add_handler
或者 add_sup_handler
向事件源注册一个观察者( Observer)
step2.使用 notify
或者 sync_notify
向事件源发送一个事件(Event)
step3.事件源将Event
广播给观察者列表Mod:handle_event
2. add_handler VS add_sup_handler
相同点:两者都能注册观察者
不同点:后者与调用proc相link,调用者proc挂掉之后,对应的观察者也丢失
3.notify VS sync_notify
两者的区别是cast与call的区别
前者:只是向事件源发送一条消息,不需要等返回值
后者:需要等返回值
用途:lager使用这个来限流
# lager_backend_throttle.erl
handle_event({log, _Message},State) ->
{message_queue_len, Len} = erlang:process_info(self(), message_queue_len),
case {Len > State#state.hwm, Len < State#state.window_min, State#state.async} of
{true, _, true} ->
%% need to flip to sync mode
?TOGGLE_SYNC(),
lager_config:set({State#state.sink, async}, false),
{ok, State#state{async=false}};
{_, true, false} ->
%% need to flip to async mode
?TOGGLE_ASYNC(),
lager_config:set({State#state.sink, async}, true),
{ok, State#state{async=true}};
_ ->
%% nothing needs to change
{ok, State}
end;
# lager.erl
case lager_config:get({Sink, async}, false) of
true ->
gen_event:notify(SinkPid, {log, LagerMsg});
false ->
gen_event:sync_notify(SinkPid, {log, LagerMsg})
end,
使用当前的邮箱中的消息数量作为判断标准,一旦超过阀值,就将异步操作关掉,这样子在调用 lager:log(...)的时候就会阻塞住,从而达到限流的目的
但是换句话说,这种限流看上去没有什么意义,
只能对当前写日志的process限流
可以spawn别的proc持续的给目标proc的信箱塞消息
4.常见用途
用来作事件的广播,非常适合日志组件造轮子,比如lager
,log4erl
5. 注意事项
由于事件广播的处理是串行的,所以尽量控制同一个事件源上面的观察者数量,由于在调度的时候对每个proc的时间片相对公平,所以观察者数量多了只有,处理变慢,消息堆积。
观察者最好不要进行耗时的处理,道理同上,如果有耗时的逻辑,请spawn另外的proc处理
如果有必要,请多开几个事件源.
总结
合理使用gen_event可以深入理解观察者模型,对事件源广播的处理变得简单。