[Erlang09]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的一个模板:你可以点击这里看源码
它随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处理。
问题:
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.