[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的一個模板:你可以點擊這里看源碼

 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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM