Gen_event行为分析和实践

1.简介

Gen_event实现了通用事件处理,通过其提供的标准接口方法以及回调函数,在OTP里面的事件处理模块是由一块通用的事件管理器和任意数量的事件处理器,并且这些事件处理器可以动态的添加和删除。一个事件可以用来记录error,alarm,info, warning等信息。一个事件管理器可以安装0,1,N个事件处理器,当一个事件管理器接受到一个事件的通知时,这个事件将会被所有的已安装的事件处理器处理(如图)。

事件管理器实质上是{Module, State}组成的列表,每个Module是一个事件处理器,State是该事件处理器的内部状态。

Gen_server接口函数与回调函数之间的关系:

  1.  1 gen_event moduleCallbackmodule
     2 -------------------------------
     3 gen_event:start_link ----->-
     4 gen_event:add_handler
     5 gen_event:add_sup_handler ----->Module:init/1
     6 gen_event:notify
     7 gen_event:sync_notify ----->Module:handle_event/2
     8 gen_event:call ----->Module:handle_call/2
     9 ------>Module:handle_info/2
    10 gen_event:delete_handler ----->Module:terminate/2
    11 gen_event:swap_handler
    12 gen_event:swap_sup_handler ----->Module1:terminate/2
    13 Module2:init/1
    14 gen_event:which_handlers ----->-
    15 gen_event:stop ----->Module:terminate/2
    16 ------>Module:code_change/3

因此,每个事件处理器都是回调模块,一个事件管理器有即可回调模块,并且可以动态的添加或删除。所以,gen_event比其它行为更能容错,如果一个事件处理器失败,但事件管理器并不会失败;如果删除一个事件处理器,并给与了一个错误的参数,但其他的事件处理器并不会受影响。

sys模块可以用来调试一个事件管理器,一个gen_event就是通过它来处理system messages.

通过回调函数的处理模块返回指定的‘hibernate‘值,让gen_event进程进入hibernate状态,但是这仅对对空闲事件比较长的服务器有用,然而这些特征应该被小心使用,因为意味着至少有两个GC,可能在忙碌的事件管理器处理事件时,你不能做你向处理的事情。特别注意:当有多个事件处理器被调用时,只要返回一个‘hibernate‘时整个事件管理器就进入了hibernate状态。

如果事件管理器不存在或者被给与了错误的参数,访问所有方法都会失败。

2.函数

2.1 导出函数

start_link() -> Result
start_link(EventMgrName) -> Result

EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}

Result = {ok,Pid} | {error,{already_started,Pid}}

监控树可以通过调用该方法创建一个事件管理器进程作为一颗监控树的一部分,并连接到监控树。

如果EventMgrName={local,Name},事件管理器在本地被注册为Name通过register/2. 如果EventMgrName={global,GlobalName},事件管理器将在全局被注册为GlobalName通过global:register_name/2.如果没有指定名字,事件管理器将不会被注册.如果EventMgrName={via,Module,ViaName},事件管理器将通过Module被注册为ViaName.Module模块应该导出register_name/2, unregister_name/1, whereis_name/1 and send/2,它的原理与global相似。

如果事件管理器被成功创建将返回{ok,Pid},如果指定名字的事件管理器已经存在将返回{error,{already_started,Pid}}

start() -> Result
start(EventMgrName) -> Result

EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}

Result = {ok,Pid} | {error,{already_started,Pid}}

创建一个独立与监控树的事件管理器。其参数与start_link/0,1一样。

add_handler(EventMgrRef, Handler, Args) -> Result

EventMgr = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()

Handler = Module | {Module,Id}

Result = ok | {‘EXIT‘,Reason} | term()

向事件管理器EventMgrRef添加一个事件处理器,事件管理器将通过回调Module:init/1初始化事件处理器和它的内部状态。
EventMgrRef可以是:

  • pid
  • Name,本地注册的事件管理器名字
  • {Name, Node},在本地另一个节点注册的事件管理器名字
  • {global,GlobalName},全局注册的事件管理器名字
  • {via,Module,ViaName},通过指定模块注册的事件管理器的名字

Handler是一个gen_event回调模块的名字或元组{Module, Id},元组{Module, Id}是一个特别的具有身份Id的gen_event回调模块,用来处理当事件处理器是同样一块gen_event回调模块的情况。

Args是任意的数据类型,用来作为Module:init/1的参数。

如果Module:init/1返回一个正确的值表明成功添加事件处理器,事件管理器的添加函数将返回ok.如果Module:init/1因为Reason失败或返回{error, Reason},这个事件处理器将被忽略并且该函数返回{‘EXIT‘,Reason}或{error, Reason}.

add_sup_handler(EventMgrRef, Handler, Args) -> Result

EventMgr = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()

Handler = Module | {Module,Id}

Result = ok | {‘EXIT‘,Reason} | term()

与add_handler/3同样的方式添加一个新的事件处理器,并且与调用用进程间建立监控。

如果调用进程因为原因Reason终止,事件管理器将回调Module:terminate/2删除该事件处理器,并以{stop, Reason}作为参数。

如果事件处理器被删除,事件管理器将发送一个消息{gen_event_EXIT,Handler,Reason}给调用进程。Reason可能是:

    • normal,如果事件处理器通过调用delete_handler/3被删除
    • shutdown,如果事件处理器因为事件管理器终止而被移除
    • {swapped,NewHandler,Pid},如果事件处理器被另一个事件处理器NewHandler替换通过swap_handler或swap_sup_handler/3.
    • Term,事件处理器因为error而被删除,Trem取决于错误类型

其他参数的描述与add_handler/3中的描述一样。

notify(EventMgrRef, Event) -> ok
sync_notify(EventMgrRef, Event) -> ok

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()

Event = term()

发送一个事件通知给事件处理器EventMgrRef.这个事件管理器将调用每个安装事件处理器的Module:handle_event/2处理这个事件。

notify/2是异步的事件通知被发送后将立即返回。sync_notify/2是同步的当事件被所有事件处理器处理后将返回ok.

对于EventMgrRef的描述参看add_handler/3.

Event是任意类型的数据格式,将被Module:handle_event/2处理。

call(EventMgrRef, Handler, Request) -> Result
call(EventMgrRef, Handler, Request, Timeout) -> Result

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()

Handler = Module | {Module,Id}

Timeout = int()>0 | infinity

Result = Reply | {error,Error}

Error = bad_module | {‘EXIT‘,Reason} | term()

向事件管理器EventMgrRef的事件处理器Handler发送一个同步请求Request,然后等待返回,或直到请求超时。事件管理器将调用Module:handle_call/2处理这个请求。

EventMgrRef and Handler的描述详见add_handler/3.

请求Request是一个任意的数据结构将被Module:handle_call/2处理。

Timeout是一个等待返回的大于0的毫秒数或者是infinity(默认5000)。如果在指定时间内没有返回这函数请求失败。

返回值Reply在Module:handle_call/2中被定义。如果指定的事件处理器Handler未安装函数将返回{error,bad_module}.如果回调函数失败因为原因Reason或返回意外值Term,这个方法将返回{error,{‘EXIT‘,Reason}}或{error,Term}.

delete_handler(EventMgrRef, Handler, Args) -> Result

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()

Handler = Module | {Module,Id}

Result = term() | {error,module_not_found} | {‘EXIT‘,Reason}

从事件管理器EventMgrRef中删除一个事件处理器Handler。事件管理器将调用Module:terminate/2来终止这个事件处理器。

对EventMgrRef and Handler的描述参见add_handler/3.

Args是一个任意类型数据结构将被传递给Module:terminate/2来处理。

返回值是Module:terminate/2的返回值。如果指定的事件处理器未安装,将返回{error,module_not_found}.如果回调函数因为Reason失败,函数将返回{‘EXIT‘,Reason}.

swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()

Handler1 = Handler2 = Module | {Module,Id}

Result = ok | {error,Error}

Error = {‘EXIT‘,Reason} | term()

在事件管理器EventMgrRef中,用一个新的事件处理器替换老的事件处理器。

第一个老的事件处理器被删除。事件管理器调用Module1:terminate(Args1, ...)删除事件处理器并保存其返回值,Module1是Handler1的回调模块。

然后新的事件处理器Handler2被添加并通过Module2:init({Args2,Term})初始化,Module2是Handler2的回调模块,Term是Module1:terminate的返回值,是Handler1传递给Handler2的消息.

新的事件处理器将被添加,即使指定的老的事件处理器未安装(Term=error);或Module1:terminate/2由于Reason失败(Term={‘EXIT‘,Reason}).老的事件处理器将被删除,即使Module2:init/1失败。

如果事件处理器Handler1与Pid间建立了监控连接,事件处理器Handler2将替代Handler1与Pid建立监控连接。

如果Module2:init/2返回一个正确的值,这个方法将返回ok.如果Module2:init/1因为Reason或返回意外值而失败这个函数将返回{error,{‘EXIT‘,Reason}}或{error,Term}.

swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()

Handler1 = Handler 2 = Module | {Module,Id}

Result = ok | {error,Error}

Error = {‘EXIT‘,Reason} | term()

在事件管理器EventMgrRef中,用一个新的事件处理器替换老的事件处理器,同时,将Handler2与调用进程间建立监控连接。

其他参数描述参见swap_handler/3.

which_handlers(EventMgrRef) -> [Handler]

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()

Handler = Module | {Module,Id}

返回事件管理器EventMgrRef中所有安装的事件处理器。

EventMgrRef and Handler的描述详见add_handler/3.

stop(EventMgrRef) -> ok

EventMgrRef = Name | {Name,Node} | {global,GlobalName} | {via,Module,ViaName} | pid()

终止事件管理器EventMgrRef.在终止之前事件管理器将调用Module:terminate(stop, ...)终止安装的事件处理器。

EventMgrRef的描述详见add_handler/3.

2.2 回调函数

Module:init(InitArgs) -> {ok,State} | {ok,State,hibernate} | {error,Reason}

InitArgs = Args | {Args,Term}

每当一个新的事件处理器添加到一个事件管理器时,这个函数将被调用来初始化这个事件处理器。

如果通过调用gen_event:add_handler/3或gen_event:add_sup_handler/3来添加事件处理器。InitArgs以Args作为参数。

如果事件处理器是通过调用gen_event:swap_handler/3或gen_event:swap_sup_handler来替换另一个事件处理器,InitArgs以{Args,Term}作为参数。

如果初始化成功,将返回{ok,State} | {ok,State,hibernate}.

如果返回{ok,State,hibernate},事件管理器将进入‘hibernate‘状态,并等待下一个事件发生。

Module:handle_event(Event, State) -> Result

Result = {ok,NewState} | {ok,NewState,hibernate}

| {swap_handler,Args1,NewState,Handler2,Args2} | remove_handler

Handler2 = Module2 | {Module2,Id}

每当事件管理器接收到通过gen_event:notify/2或gen_event:sync_notify/2发送过来的事件时,每个安装的事件处理器将调用该方法处理事件。

Event和notify/sync_notify的参数匹配。

如果这个函数返回{ok, NewState}或{ok, NewState, hibernate}该事件处理器保持在事件管理器内部,并更新内部状态。

如果任一事件管理器返回的是{ok,NewState,hibernate},事件管理器将进入‘hibernate‘状态,等待下一个事件的产生。

如果函数返回{swap_handler,Args1,NewState,Handler2,Args2}该事件处理器将被Handler2替换,通过调用Module:terminate(Args1,NewState)和Module2:init({Args2,Term})进行替换。详情参见gen_event:swap_handler/3.

如果该函数返回remove_handler,该事件管理器将被通过调用Module:terminate(remove_handler,State)删除。

Module:handle_call(Request, State) -> Result

Result = {ok,Reply,NewState} | {ok,Reply,NewState,hibernate}

| {swap_handler,Reply,Args1,NewState,Handler2,Args2}

| {remove_handler, Reply}

Handler2 = Module2 | {Module2,Id}

每当一个事件管理器接收到来自gen_event:call/3,4的请求时,这个函数将被指定的事件处理器调用来处理这个请求。

该函数的返回值参见handle_event/2,同时包含了一个任意的数据结构Reply,它将返回给调用端。

Module:handle_info(Info, State) -> Result

Result = {ok,NewState} | {ok,NewState,hibernate}

| {swap_handler,Args1,NewState,Handler2,Args2} | remove_handler

Handler2 = Module2 | {Module2,Id}

当一个事件管理器接收到其他信息(除event,synchronous request,system message)每个安装的事件处理器都会调用该函数处理这个消息。

返回参数详情见handle_event/2.

Module:terminate(Arg, State) -> term()

Arg = Args | {stop,Reason} | stop | remove_handler

| {error,{‘EXIT‘,Reason}} | {error,Term}

Args = Reason = Term = term()

每当一个事件处理器从事件管理器中被删除时,该函数被调用。它与Module:init/1相反用来做一些清理工作。

如果该事件管理器是调用gen_event:delete_handler, gen_event:swap_handler/3 or gen_event:swap_sup_handler/3来删除,Arg就是他们调用时的参数信息Args.

如果事件处理器相监控连接的进程由于Reason终止时,Arg ={stop, Reason}.

如果由于事件管理器终止而终止事件处理器时,Arg = stop.

如果事件管理器是监控树的一部分并被监控树按顺序终止。甚至它不是监控树的一部分,如过它接受到来自父进程的退出信号‘EXIT‘都将被终止。

如果时间处理器被删除由于另一个回调函数返回了remove_handler or {remove_handler,Reply},Arg = remove_handler.

如果事件处理器被删除是因为回调函数返回了一个意外值Term,Arg={error,Term};如果回调函数失败,Arg={error,{‘EXIT‘,Reason}}.

该函数将返回任意类型的值。如果事件处理器被删除是由于调用gen_event:delete_handler,那个函数的返回值就是这个函数的返回值。如果事件处理器被另一个时间处理器替换,返回值将被作为另一个事件处理器的初始化参数。否则,返回值将被忽略。

Module:code_change(OldVsn, State, Extra) -> {ok, NewState}

该函数主要用于版本的热更新,后续相关专题会介绍。

3.实践

首先通过gen_event:start_link/1启动一个事件管理器:
  1. 1 start_link()-> 2 gen_event:start_link({local,?SERVER}).

同时终止管理器的方法为:

  1. 1 stop()-> 2 gen_event:stop(?SERVER).

当终止事件管理器时会调用事件处理器的Module:terminate(stop, ...),来依次关闭事件处理器。

3.1 添加删除事件

添加事件处理器:

  1. 1 add_handler()->
    2 gen_event:add_handler(?SERVER, myevent,[]),
    3 gen_event:add_sup_handler(?SERVER, myerror,[]).

将会调用Module:init/1初始化事件处理器。

删除事件处理器:

  1. 1 delete()->
    2 gen_event:delete_handler(?SERVER,myerror,[]).

将会调用Module:terminate/2来终止事件处理器。

3.2 事件消息
  1. 1 notity()->
    2 gen_event:notify(?SERVER,liuwei). %%异步通知
    3 notity_sync()->
    4 gen_event:sync_notify(?SERVER,liuwei_sync).   %%同步通知
    5 handle_event(_Event,State)->
    6 timer:sleep(2000),
    7 io:format("Event:~p~n",[_Event]),
    8 {ok,State}.
当返回{ok, NewState}时,事件管理器不变,并更新内部状态。

当返回{swap_handler,Args1,NewState,Handler2,Args2}时,将替换老的事件管理器:

当返回remove_handler时,将删除事件处理器:

3.3 事件交换
  1. 1 swap()->
    2 gen_event:swap_handler(?SERVER,{myevent,normal_swap},{myerror,[]}).

首先会调用myevent:terminate(normal_swap, ...)来终止myevent事件处理器,并返回数据Term;然后调用myerror:init({[],Term})来启动myerror事件处理器。

特别注意:新的事件处理器将被添加,即使指定的老的事件处理器未安装(Term=error);或Module1:terminate/2由于Reason失败(Term={‘EXIT‘,Reason}).老的事件处理器将被删除,即使Module2:init/1失败。

       

3.4 监控事件
创建进程与事件管理器的监控连接:

  1.  1 %%启动服务器add1
     2 start_link()->
     3 gen_server:start({local,?SERVER},?MODULE,[],[]).
     4 %%启动事件管理器myevent
     5 start_link()->
     6 {Flag,Pid}= gen_event:start_link({local,?SERVER}).
     7 %%添加连接事件处理器
     8 add_sup_handler()->
     9 gen_server:call(?SERVER, add_sup_handler).
    10 handle_call(add_sup_handler,_From,State)->
    11 myevent:add_sup_handler(),
    12 {reply, ok,State};
    13 add_sup_handler()->
    14 gen_event:add_sup_handler(?SERVER, myerror,["add_sup_handler:myerror"]).
    15 %%删除连接的事件处理器
    16 delete()->
    17 gen_event:delete_handler(?SERVER,myerror,[]).
    18 %%接受事件处理器退出信息
    19 handle_info(_Info,State)->
    20 io:format("add1::info:~p~n",[_Info]),
    21 {noreply,State}.

当事件处理器正常删除时(delete_handler/2):

当因为事件管理器终止时(stop/1):

当事件处理器因为替换而终止时(swap_handler/2):

当事件处理器因为error而终止时:

当调用进程终止时(Module:terminate({stop, Reason}, ...)来终止事件处理器):

gen_event:swap_sup_handler/3与上面一样,当老的事件处理器在替换成新的事件处理器时,将与调用进程间建立监控连接。

当我们在调用gen_event:swap_handler/3时,用老的事件处理器去替换新的事件处理器,当老的事件处理器与一个进程建立监控连接时,这时这个进程将与新的事件处理器建立监控连接:

4.总结

通过Gen_event我们可以实现通用的事件管理器,用于记录一些消息、警告、错误的信息。gen_event提供了标准的接口和回调函数来实现事件管理器与事件处理器。事件管理器可以动态的添加替换事件处理器,也可以动态的删除事件处理器。当一个事件到达时,所有安装的事件处理器都会处理该事件信息。事件管理器具有比其他行为模块的容错能力,当一个事件处理器失败时,并不会影响其他事件处理器。

博客地址:http://www.cnblogs.com/liuweiccy/p/4658588.html

源码地址:https://github.com/liuweiccy/test_sup_multi/tree/master/test_sup_gen_event

优秀的代码是艺术品,它需要精雕细琢!

来自为知笔记(Wiz)

时间: 2024-10-10 08:30:36

Gen_event行为分析和实践的相关文章

Log4j2分析与实践

当前网络上关于Log4j2的中文文章比较零散,这里整理了一下关于Log4j2比较全面的一些文章,供广大技术人员参考 Log4j2分析与实践-认识Log4j2 Log4j2分析与实践-架构 Log4j2分析与实践-配置示例 Log4j2分析与实践-配置详解 Log4j2分析与实践-Lookups Log4j2分析与实践-Appenders Log4j2分析与实践-Layouts Log4j2分析与实践-Filters Log4j2分析与实践-异步Logger Log4j2分析与实践-无垃圾地写日志

Supervisor行为分析和实践

1.简介 Erlang要编写高容错性.稳定性的系统,supervisor就是用来解决这一问题的核心思想.通过建立一颗监控树,来组织进程之间的关系,通过确定重启策略.子进程说明书等参数信息来确定佣程与督程的行为,以及在发生故障时的处理办法.简单介绍supervisor的API:      start_link(Module, Args) -> startlink_ret()      start_link(SupName, Module, Args) -> startlink_ret() 用来启

Gen_server行为分析与实践

1.简介 Gen_server实现了通用服务器client_server原理,几个不同的客户端去分享服务端管理的资源(如图),gen_server提供标准的接口函数和包含追踪功能以及错误报告来实现通用的服务器,同时可以作为OTP监控树的一部分. Gen_server函数与回调函数之间的关系: 1 gen_server module Callback module 2 ----------------- --------------- 3 gen_server:start_link ----->

[测试技术思考] 软件可测性分析和实践

软件测试中可测性一般是指对系统的可控性.可观测性进行的评估,借以反映系统设计.实现对测试的友好程度和相应的测试成本.可测性在测试阶段会对系统的测试成本及关联产品代码的Patch次数产生重大影响.如何提高可测性成为软件生命周期特别是前期(设计阶段.coding阶段)重要的一环. 本文带领大家探索在实际项目中可测性相关的实战经验和对应的改进措施. 1 提高可测性的切入点 可测性的评估和改进最早开始于两个阶段: a. 新项目的设计阶段: b. 已有项目新功能.新策略的提测阶段. 这些是提高团队设计的系

基于redis的分布式锁的分析与实践

转:https://my.oschina.net/wnjustdoit/blog/1606215 前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于线程之间是否相互阻塞. 那么,本文主要来讨论基于redis的分布式锁算法问题. 从2.6.12版本开始,redis为SET命令增加了一系列选项(SET key value [EX seconds] [PX

自定义View系列教程04--Draw源码分析及其实践

通过之前的详细分析,我们知道:在measure中测量了View的大小,在layout阶段确定了View的位置. 完成这两步之后就进入到了我们相对熟悉的draw阶段,在该阶段真正地开始对视图进行绘制. 按照之前的惯例,我们来瞅瞅View中draw( )的源码 public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFL

手动SQL注入原理分析与实践

代码仓库 本文所用代码的代码库地址: 点击这里前往Github仓库 了解SQL注入 定义 SQL注入攻击(SQL Injection),简称注入攻击,是Web开发中最常见的一种安全漏洞.可以用它来从数据库获取敏感信息,或者利用数据库的特性执行添加用户,导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限. 原理 造成SQL注入的原因是因为程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的SQL查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查

Gearman介绍、原理分析、实践改进

gearman是什么? 它是分布式的程序调用框架,可完成跨语言的相互调用,适合在后台运行工作任务.最初是2005年perl版本,2008年发布C/C++版本.目前大部分源码都是(Gearmand服务job Server)C++,各个API实现有各种语言的版本.PHP的Client API与Worker API实现为C扩展,在PHP官方网站有此扩展的中英文文档. gearman架构中的三个角色 client:请求的发起者,工作任务的需求方(可以是C.PHP.Java.Perl.Mysql udf等

分享一次失败的项目实践经验

一.开篇 最近在网上看到了一款canvas实现网页涂鸦效果的作品,感觉这个效果比较奇特而且在以前没有学习canvas这样的功能是不可思议的,所以本人秉着程序员的那一份执着,花了两三个小时的时间来研究了一下canvas涂鸦作品的代码,发现里面代码比较精辟,但是美中不足的是有些代码的结构会比较的混乱,让人感觉层次上面有点不太分明.所以本人就打算对这个代码结构进行重构使其更具有可读性.但是理想是丰满的现实是骨感的,经过这一次代码的分析和实践下来,对我也是打击蛮大的,以前从来都是认为前端只要能够熟悉调试