Erlang Custom Timer implementation
- Game server development timer design
- Need to combine the development and implementation of the game framework to make choices
Reference
Design
timer process distribution
- Independent timer process management and distribution of short message processing,
- Reduce the use of receive / after in the system, that is, reduce the use of timer timers
gen_server timeout mechanism
- Based on the gen_server timeout mechanism, use gb_trees to manage timer data
- The business calculates the next trigger time according to the needs and adds it to the timer data
receive / after test
-module(mod_receive_after).
-export([
test1/1,
recev1/0,
test2/1,
recev2/0
]).
test1(Number) ->
List = lists:seq(1, Number, 1),
[spawn(?MODULE, recev1, []) || _ <- List],
ok.
recev1() ->
receive
test -> stop
end.
test2(Number) ->
List = lists:seq(1, Number, 1),
[spawn(?MODULE, recev2, []) || _ <- List],
ok.
recev2() ->
receive
test -> stop
after 1000 ->
recev2()
end.
- Run test1
erl +P 2500000
mod_receive_after:test1(2000000).
ok
- Run test2 【Modify after 1000 to after 10000】
erl +P 2500000
mod_receive_after:test2(2000000).
ok
- After time is shorter, test2 battle uses more CPU
- Timers require corresponding overhead
timer process distribution example
timer_server.erl
do_reg(PID) ->
OldPIDList =
case erlang:get(reg_pid_list) of
[_ | _] = OldPIDListT -> OldPIDListT;
_ -> []
end,
erlang:put(reg_pid_list, [PID | OldPIDList]),
ok.
do_loop() ->
PIDList =
case erlang:get(reg_pid_list) of
[_ | _] = PIDListT -> PIDListT;
_ -> []
end,
do_loop(PIDList).
do_loop([]) ->
ok;
do_loop([PID | PIDList]) ->
erlang:send(PID, {loop_message}),
do_loop(PIDList).
- Register processes that need to be called cyclically
- Use erlang:send_after/3 to execute the do_loop function on a regular basis
- Send a scheduled message to all registered processes
gen_server timeout mechanism example
- receive_after_server.erl
init([Timeout]) ->
process_flag(trap_exit, true),
case Timeout of
infinity ->
erlang:send(erlang:whereis(timer_server), {reg, erlang:self()}),
{ok, #state{interval = Timeout}};
_ ->
{ok, #state{interval = Timeout}, Timeout}
end.
handle_call(Request, _From, #state{interval = infinity} = State) ->
{reply, Request, State};
handle_call(Request, _From, #state{interval = Timeout} = State) ->
{reply, Request, State, Timeout}.
handle_cast(_Request, #state{interval = infinity} = State) ->
{noreply, State};
handle_cast(_Request, #state{interval = Timeout} = State) ->
{noreply, State, Timeout}.
handle_info({'EXIT', _, _Reason}, State) ->
{stop, normal, State};
handle_info({loop_message}, #state{interval = infinity} = State) ->
{noreply, State};
handle_info(_Info, #state{interval = infinity} = State) ->
{noreply, State};
handle_info(timeout, #state{interval = Timeout, times = Times} = State) ->
{noreply, State#state{times = Times + 1}, Timeout};
handle_info(_Info, State) ->
{noreply, State}.
- Timeout mechanism {ok, State, Timeout} triggers handle_info({timeout, State) message
- After the process processes other messages, it needs to determine the next timeout time to implement timing
Source Code
- timer_server.erl
-module(timer_server).
-behaviour(gen_server).
-export([
start_link/0
]).
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-record(state, {}).
%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init([]) ->
process_flag(trap_exit, true),
erlang:send_after(1000, erlang:self(), {loop, 1000}),
{ok, #state{}}.
handle_call(Request, _From, State) ->
{reply, Request, State}.
handle_cast(_Request, State) ->
{noreply, State}.
handle_info({'EXIT', _, _Reason}, State) ->
{stop, normal, State};
handle_info({reg, PID}, State) ->
do_reg(PID),
{noreply, State};
handle_info({loop, 1000}, State) ->
erlang:send_after(1000, erlang:self(), {loop, 1000}),
do_loop(),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
do_reg(PID) ->
OldPIDList =
case erlang:get(reg_pid_list) of
[_ | _] = OldPIDListT -> OldPIDListT;
_ -> []
end,
erlang:put(reg_pid_list, [PID | OldPIDList]),
ok.
do_loop() ->
PIDList =
case erlang:get(reg_pid_list) of
[_ | _] = PIDListT -> PIDListT;
_ -> []
end,
do_loop(PIDList).
do_loop([]) ->
ok;
do_loop([PID | PIDList]) ->
erlang:send(PID, {loop_message}),
do_loop(PIDList).
- receive_after_server.erl
-module(receive_after_server).
-behaviour(gen_server).
-export([
start1/1,
start2/1
]).
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-record(state, {interval, times = 0}).
%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================
gen_name(I) ->
erlang:list_to_atom(lists:concat([?MODULE, "_", I])).
start1(Number) ->
List = lists:seq(1, Number, 1),
[
begin
PName = gen_name(I),
{ok, _} = gen_server:start_link({local, PName}, ?MODULE, [infinity], [])
end
|| I <- List
],
ok.
start2(Number) ->
List = lists:seq(1, Number, 1),
[
begin
PName = gen_name(I),
{ok, _} = gen_server:start_link({local, PName}, ?MODULE, [1000], [])
end
|| I <- List
],
ok.
init([Timeout]) ->
process_flag(trap_exit, true),
case Timeout of
infinity ->
erlang:send(erlang:whereis(timer_server), {reg, erlang:self()}),
{ok, #state{interval = Timeout}};
_ ->
{ok, #state{interval = Timeout}, Timeout}
end.
handle_call(Request, _From, #state{interval = infinity} = State) ->
{reply, Request, State};
handle_call(Request, _From, #state{interval = Timeout} = State) ->
{reply, Request, State, Timeout}.
handle_cast(_Request, #state{interval = infinity} = State) ->
{noreply, State};
handle_cast(_Request, #state{interval = Timeout} = State) ->
{noreply, State, Timeout}.
handle_info({'EXIT', _, _Reason}, State) ->
{stop, normal, State};
handle_info({loop_message}, #state{interval = infinity} = State) ->
{noreply, State};
handle_info(_Info, #state{interval = infinity} = State) ->
{noreply, State};
handle_info(timeout, #state{interval = Timeout, times = Times} = State) ->
{noreply, State#state{times = Times + 1}, Timeout};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
- mod_receive_after.erl
-module(mod_receive_after).
-export([
test1/1,
recev1/0,
test2/1,
recev2/0
]).
test1(Number) ->
List = lists:seq(1, Number, 1),
[spawn(?MODULE, recev1, []) || _ <- List],
ok.
recev1() ->
receive
test -> stop
end.
test2(Number) ->
List = lists:seq(1, Number, 1),
[spawn(?MODULE, recev2, []) || _ <- List],
ok.
recev2() ->
receive
test -> stop
after 1000 ->
recev2()
end.
Test
Method A: timer process distribution
erl +P 2500000 +t 1073741824
timer_server:start_link().
receive_after_server:start1(2000).
ok
Method B: timeout mechanism
erl +P 2500000 +t 1073741824
receive_after_server:start2(2000).
ok
Conclusion
It is more convenient to develop business logic in the business module using the timing round-robin method
The business only needs to register the loop call method, and determine the next trigger time in the loop and execute it
The overall business framework is simpler