首页 > 代码库 > [Erl_Question09]Erlang gen_server实现定时器(interval)的几种方法及各自的优缺点?

[Erl_Question09]Erlang gen_server实现定时器(interval)的几种方法及各自的优缺点?

方法1:

%%gen_server:部分call_back function.
   -define(TIME,1000).

   init([]) –>
      erlang:send_after(?TIME,self(),loop_interval_event),
      {ok, #state{}}.

   handle_info(loop_interval_event, State) –>
      NewState = do_loop_interval_event(State),
      erlang:send_after(?TIME,self(),loop_interval_event),
      {noreply, NewState}.

优点:可以加任意多个定时器,且可以保证do_loop_interval_event/1处理完后才触发第二个定时器【想像一个如果处理event 200ms,处理间隔是150ms,那么这个进程还是可以不阻塞消息队列的】.这种方法也是推荐使用的。

缺点: 如果项目有很多的进程都有定时器,大家都调用系统函数来判定时间,性能消耗会增大【这个=下讲原因】。

方法2:

%%在gen_server:call back function 返回值加入一个时间

    init([]) –>
     {ok, #state{},?TIME}.

   handle_info(timeout,State = #state{count = Count}) –>
    io:format("timeout:~w~n",[Count]),
    {noreply,State#state{count = Count+1},?TIME}.

原理:利用gen_server: init返回值如果是{ok,State,TimeTemp}时会在TimeTemp后发出一个timeout信息,给handle_info处理,然后handle_info处理后再设置返回值的time,又会循环触发这个timeout事件,完成定循环功能

缺点:只能使用一个定时事件哦,就是timeout,且会被其它的call back function :handle_call,handle_cast,影响,因为如果他们的返回值也加入这个TIME,也会触发同一个timeout事件….

其实gen_server可以设置这个timeout事件,主要目的还是为了怕回调函数处理消息太慢,如果太慢了,就执行相同的timeout做相关处理。

方法3:

%%使用timer:send_interval/3设置事件间隔
    init([]) –>
      timer:send_interval(?TIME,self(),loop_interval_event),
     {ok, #state}.   
   handle_info(loop_interval_event, State) ->
      NewState = do_loop_inverval_event(State),
      {noreply, NewState};

send_interval/3

Evaluates Pid ! Message repeatedly after Time amount of time has elapsed. (Pid can also be an atom of a registered name.) Returns {ok, TRef} or {error, Reason}.

就是每隔TIME时间就给Pid发一个Msg,(相当于每TIME Pid ! Msg).

缺点:timer自已本身就是一个gen_server进程,如果在SMP下大量进程要使用这个进程来频繁调度也是很吃力的.

优点:当然是简单且可以设置多个。


  以上只是小菜,下面来看看erlang 神秘的time及为什么大部分人都视timer模块为毒药?

erlang使用time有4种方式:

语法层:receive after opcode实现,timeout立即把进程加入到调度队列 使用非常多,也是最高效的
BIF:
erlang: send_after/3
erlang: send_timer/3

timout 立即给Dest Pid发送Msg

使用较多
gen_server:
timer模块
使用gen_server统一管理用一个ets的管理实现 统一管理erts 定时器
driver:
int driver_set_timer(ErlDrvPort port, unsigned long time);
tcp/udp进程需要超时处理,所以有大量的连接的时候这种timer的数量非常大,定时器超时后把port_task加到调度队列 inet_driver大量使用这个api. 这个没用到,不是很懂…

这上面只有timer模块是用erlang写的,那么,我们来好好研究下这个简单而有趣的timer模块吧。

timer是一个典型的gen_server模块,非常简单明了,只有500行左右,也可以做为学习写好gen_server的一个模板:你可以点击这里看源码

 timer

 

它随kernel application起动,被kernel_safe_sup监控,注册名为timer_server。是一个标准的gen_server模块,我们现在来看看它有趣的地方:

它的实现主要是依赖于gen_server call back里面如果:init/1,hande_call/3 ,handle_info/2,handle_cast/2 返回值加入TimeOut参数,那么经过TimeOut时间后,会触发一个timeout事件给handle_info处理。

timer_01

问题:

1. 为什么大部分人视timer为毒药呢?他的局限性是什么?

timer设计的目的就是:统一管理多少时长后发生的事件,是一个manager进程,同时也是一个单进程,这样的短板:如果大量的事件都在这里面时,就会使这个进程负荷太大,出现各种不稳定bug.这就是大部分人不也使用它的原因;

2. 什么时候可以使用它呢?

首先,要明白为什么为把这些事件用timer来统一管理,因为如果大量进程自己内部调用erlang: send_after/3,即当用erlang: send_after/3导致的开销大于使用timer的开销时,自然,我们就会想自己设计一个统一的管理进程来取代每个work进程自己单独用erlang: send_after/3发信息处理:即manger进程 每隔一段时间就给work进程发消息来代替erlang: send_after/3. 自己造这个轮子也可以,不过在这时使用timer模块来处理再好不过啦!!!!

Tip:你可以使用timer:get_status()来查看这个进程的负载情况

关于timer的误解:

1. 所有timer模块都是单进程的,使用一定要慎重考虑再考虑!

The functions in the timer module that do not manage timers (such as timer:tc/3 or timer:sleep/1), do not call the timer-server process and are therefore harmless.

有一些timer内不管理时间定时器的函数 例如:

sleep/1,
   tc/1, tc/2, tc/3, 
   now_diff/2,
   seconds/1, minutes/1, hours/1, hms/3

不去调用定时器server进程的函数都是无害的。【也就是说:有些timer里面的函数是不依赖于这个server的,可以随意用】

2. Warning:

A timer can always be removed by calling cancel/1.

An interval timer, i.e. a timer created by evaluating any of the functions apply_interval/4, send_interval/3, and send_interval/2, is linked to the process towards which the timer performs its task.

A one-shot timer, i.e. a timer created by evaluating any of the functions apply_after/4, send_after/3, send_after/2, exit_after/3, exit_after/2, kill_after/2, and kill_after/1 is not linked to any process. Hence, such a timer is removed only when it reaches its timeout, or if it is explicitly removed by a call to cancel/1.