erlang定时器

目录:

   erlang:send_after和erlang:start_timer的使用解释

   erlang的timer和实现机制

   erlang定时器的强度测试

 

erlang:send_after和erlang:start_timer的使用解释

转自:http://mryufeng.iteye.com/blog/634815

 

前段时间arksea 同学提出这个问题, 因为文档里面写的很不明白.

    erlang:send_after(Time, Dest, Msg) -> TimerRef
    Time = int()
    0 <= Time <= 4294967295
    Dest = pid() | RegName
    LocalPid = pid() (of a process, alive or dead, on the local node)
    Msg = term()
    TimerRef = ref()
    Starts a timer which will send the message Msg to Dest after Time milliseconds.

    If Dest is an atom, it is supposed to be the name of a registered process. The process referred to by the name is looked up at the time of delivery. No error is given if the name does not refer to a process.

    If Dest is a pid, the timer will be automatically canceled if the process referred to by the pid is not alive, or when the process exits. This feature was introduced in erts version 5.4.11. Note that timers will not be automatically canceled when Dest is an atom.

    See also erlang:start_timer/3, erlang:cancel_timer/1, and erlang:read_timer/1.

    Failure: badarg if the arguments does not satisfy the requirements specified above.

    erlang:start_timer(Time, Dest, Msg) -> TimerRef
    Time = int()
    0 <= Time <= 4294967295
    Dest = LocalPid | RegName
    LocalPid = pid() (of a process, alive or dead, on the local node)
    RegName = atom()
    Msg = term()
    TimerRef = ref()
    Starts a timer which will send the message {timeout, TimerRef, Msg} to Dest after Time milliseconds.

    If Dest is an atom, it is supposed to be the name of a registered process. The process referred to by the name is looked up at the time of delivery. No error is given if the name does not refer to a process.

    If Dest is a pid, the timer will be automatically canceled if the process referred to by the pid is not alive, or when the process exits. This feature was introduced in erts version 5.4.11. Note that timers will not be automatically canceled when Dest is an atom.

    See also erlang:send_after/3, erlang:cancel_timer/1, and erlang:read_timer/1.

    Failure: badarg if the arguments does not satisfy the requirements specified above.

表面上看这2个API没有什么大的差别,使用上也一样, 那为什么要搞二个呢? 好奇怪!

好, 让我们来好好研究下典型应用.

这2个API都返回 TimerRef. 用户可以用这个TimerRef来取消定时器. 唯一的差别是在超时的时候发送的消息不同: send_after是Msg, start_timer是{timeout, TimerRef, Msg}.
问题就出在取消timer的时候. 如果这个timer还没有超时的时候, 那么取消就没问题. 如果超时了麻烦就来了, 这个消息已经有可能已经被放到目标进程的消息队列里,等待派遣处理了.

这时候send_after里面存放的是Msg, 那用户如何知道Msg是对于那个TimerRef的呢? 读者可能说, 那我可以在消息里面加入TimerRef. 这个主意不错, 但是问题是在send_after调用返回之前, 你是无法得到TimerRef, 当然也就无从构造这个消息, 那就无法处理这个可能的超时信息, 就会破坏逻辑.
所以erts version 5.4.11 引入了, start_timer来解决这个问题. 它是自动的在超时后, 要发送消息前, 在消息里面添加了{timeout, TimerRef, Msg}, 达到识别的目的.

结论: 文档里面一眼带过的东西, 其实是有很多设计方面的考虑, 要认真考虑它的存在的意义.
 
==================================

erlang的timer和实现机制

转自:http://www.iteye.com/topic/288395

对于任何网络程序来讲,定时器管理都是重头戏。erlang更是依赖于定时器。基础的timer主要是由time.c erl_time_sup.c实现。timer是基于time wheel的实现,支持time jump detection and correction。 上层的erl_bif_timer.c io.c中实现。

erl +c
    Disable compensation for sudden changes of system time.
    Normally, erlang:now/0 will not immediately reflect sudden changes in the system time, in order to keep timers (including receive-after) working. Instead, the time maintained by erlang:now/0 is slowly adjusted towards the new system time. (Slowly means in one percent adjustments; if the time is off by one minute, the time will be adjusted in 100 minutes.)
    When the +c option is given, this slow adjustment will not take place. Instead erlang:now/0 will always reflect the current system time. Note that timers are based on erlang:now/0. If the system time jumps, timers then time out at the wrong time.

erlang使用timer有3中方式:
1. 语法层面的 receive ... after ...
   这个是opcode实现的,一旦timeout立即把process加到调度队列,使用频度比较高。

2. bif 
   erlang:send_after(Time, Dest, Msg) -> TimerRef
   erlang:start_timer(Time, Dest, Msg) -> TimerRef
   这个一旦timeout就给Dest发送一条消息,使用频度不是很高。

3.driver层面的。
   int driver_set_timer(ErlDrvPort port, unsigned long time);
   inet_driver大量使用这个api. tcp/udp进程需要超时处理,所以有大量的连接的时候这种timer的数量非常大。定时器超时后把port_task加到调度队列。

定时器的最早超时时间用于poll的等待时间。

整个定时器由bump_timer来驱动。bump_timer是在schedule的时候不定期调用的。总之使用timer的时候要小心,因为timer实在scheduler的线程里面调用的,不能做非常耗时的操作,否则会阻塞调度器。

==========================

erlang定时器的强度测试

转自:http://www.iteye.com/topic/288395

erlang的定时器在做网络程序的时候几乎无所不在, 语法层面的receive after,IO操作超时,driver内部等都大量使用timer,特别是tcp 在发送接收都有个超时。 如果你有大量的tcp链接, 就意味着大量的定时器。 那么定时器的性能就是个很大的考验。erts的定时器是个timer_wheel实现, 和linux内核用的差不多,大概支持百万级别的规模。 测试如下:

并发开N个进程 每个进程里面0-10秒的随机定时,模拟tcp超时的情况。每个定时器事件的流程是这样的 进程检查消息队列 没消息 注册定时器事件 进程换出 定时器超时 进程换入 处理定时器事件。

root@nd-desktop:~/test# cat ttimer.erl
-module(ttimer).
-export([start/1]).

upmap(F, L) ->
    Parent = self(),
    Ref = make_ref(),
    [receive {Ref, Result} -> Result end
     || _ <- [spawn(fun() -> Parent ! {Ref, F(X)} end) || X <- L]].

loop(0)->
    ok;

loop(Cnt)->
        receive after random:uniform(10000) -> cont end,
        loop(Cnt-1).

start([A1, A2]) ->
         Start= now(),
         N= list_to_integer(atom_to_list(A1)),
         Cnt = list_to_integer(atom_to_list(A2)),
         io:format("spawn ~w process, loop ~w~n", [N, Cnt]),
         upmap(fun loop/1, lists:duplicate(N, Cnt)),
         io:format("run ~w ms~n", [round(timer:now_diff(now(), Start) /1000)]),
         done.


root@nd-desktop:~/test# erl -smp disable -noshell +P 9999999 -s ttimer start 500000 10 -s erlang halt
spawn 500000 process, loop 10
run 63201 ms

单cpu保持在70-80%, 63秒处理了500W个定时器事件, 大概每秒8W.

root@nd-desktop:~/test# cat /proc/cpuinfo
model name      : Pentium(R) Dual-Core  CPU      E5200  @ 2.50GHz
bogomips        : 4987.08

结论: 定时器处理还是比较费时间的。

 

 

 
posted @ 2012-12-18 14:15  小壁虎借尾巴  阅读(2293)  评论(0编辑  收藏  举报