【发布时间】:2016-12-29 11:10:36
【问题描述】:
我想每 10ms 运行一次周期性的 erlang 进程(基于挂钟时间),10ms 应该尽可能准确;实施它的正确方法应该是什么?
【问题讨论】:
我想每 10ms 运行一次周期性的 erlang 进程(基于挂钟时间),10ms 应该尽可能准确;实施它的正确方法应该是什么?
【问题讨论】:
如果您想要真正可靠和准确的周期性过程,您应该使用erlang:monotonic_time/0,1 依赖实际的挂钟时间。如果你使用Stratus3D的answer中的方法,你最终会落后。
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}.
你可以在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).
【讨论】:
如果您真的想尽可能精确,并且您确定您的任务所花费的时间将少于您希望它执行的时间间隔,那么您可以有一个长时间运行的进程,而不是每 10 毫秒生成一个进程。 Erlang 可以每 10 毫秒生成一个新进程,但除非有理由不能重用同一个进程,否则通常不值得开销(即使它很少)。
我会在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}.
然后像这样启动 gen_server:
periodic_task:start_link().
只要 gen_server 正在运行(如果它崩溃,父进程也会因为它们被链接),函数do_task/0 将几乎每 10 毫秒执行一次。请注意,这不会完全准确。执行时间会有偏差。实际间隔为 10ms + 收到定时器消息,取消旧定时器,启动新定时器的时间。
如果您想每 10 毫秒启动一个单独的进程,您可以让 do_task/0 生成一个进程。请注意,这会增加额外的开销,但不一定会降低生成间隔的准确性。
我的例子取自这个答案:What's the best way to do something periodically in Erlang?
【讨论】: