How to run a Erlang Process periodically with Prec

2019-02-14 00:04发布

I want to run a periodic erlang process every 10ms (based on wall clock time), the 10ms should be as accurate as possible; what should be the right way to implement it?

标签: timer erlang
2条回答
贪生不怕死
2楼-- · 2019-02-14 00:36

If you really want to be as precise as possible and you are sure your task will take less time than the interval you want it performed at you could have one long running process instead of spawning a process every 10ms. Erlang could spawn a new process every 10ms but unless there is a reason you cannot reuse the same process it's usually not worth the overhead (even though it's very little).

I would do something like this in an OTP gen_server:

-module(periodic_task).

... module exports

start_link() ->
  gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

... Rest of API and other OTP callbacks

init([]) ->
  Timer = erlang:send_after(0, self(), check),
  {ok, Timer}.

handle_info(check, OldTimer) ->
  erlang:cancel_timer(OldTimer),
  Timer = erlang:send_after(10, self(), check),
  do_task(), % A function that executes your task
  {noreply, Timer}.

Then start the gen_server like this:

periodic_task:start_link().

As long as the gen_server is running (if it crashes so will the parent process since they are linked) the function do_task/0 will be executed almost every 10 milliseconds. Note that this will not be perfectly accurate. There will be a drift in the execution times. The actual interval will be 10ms + time it takes receive the timer message, cancel the old timer, and start the new one.

If you want to start a separate process every 10ms you could have the do_task/0 spawn a process. Note that this will add additional overhead, but won't necessarily make the interval between spawns less accurate.

My example was taken from this answer: What's the best way to do something periodically in Erlang?

查看更多
【Aperson】
3楼-- · 2019-02-14 00:43

If you want really reliable and accurate periodic process you should rely on actual wall clock time using erlang:monotonic_time/0,1. If you use method in Stratus3D's answer you will eventually fall behind.

start_link(Period) when Period > 0, is_integer(Period) ->
  gen_server:start_link({local, ?SERVER}, ?MODULE, Period, []).

...

init(Period) ->
    StartT = erlang:monotonic_time(millisecond),
    self() ! tick,
    {ok, {StartT, Period}}.

...

handle_info(tick, {StartT, Period} = S) ->
    Next = Period - (erlang:monotonic_time(millisecond)-StartT) rem Period,
    _Timer = erlang:send_after(Next, self(), tick),
    do_task(),
    {noreply, S}.

You can test in the shell:

spawn(fun() ->
    P = 1000,
    StartT = erlang:monotonic_time(millisecond),
    self() ! tick,
    (fun F() ->
        receive
          tick ->
            Next = P - (erlang:monotonic_time(millisecond)-StartT) rem P,
            erlang:send_after(Next, self(), tick),
            io:format("X~n", []),
            F()
        end
      end)()
  end).
查看更多
登录 后发表回答