Skip to content

Instantly share code, notes, and snippets.

@zhongwencool
Created June 26, 2014 06:54
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save zhongwencool/eca6609b59ed635de164 to your computer and use it in GitHub Desktop.
Save zhongwencool/eca6609b59ed635de164 to your computer and use it in GitHub Desktop.
一个简单的分层时间轮实现:类似于3维数组实现时钟
%%%-------------------------------------------------------------------
%%% @author zhongwencool@gmail.com
%%% @doc 一个简单的分层时间轮实现:类似于3维数组实现时钟
%%% 根据 http://www.embeddedlinux.org.cn/RTConforEmbSys/5107final/LiB0071.html 实现
%%% 1.时钟原理说明:
%%% 1.1. 初始化一个三层时间轮:秒刻盘:0~59个SecList, 分刻盘:0~59个MinList, 时刻盘:0~12个HourList;
%%% 1.2. SecTick由外界推动,每跳一轮(60格),SecTick复位至0,同时MinTick跳1格;
%%% 1.3. 同理MinTick每跳一轮(60格),MinTick复位至0,同时HourTick跳1格;
%%% 1.4. 最高层:HourTick跳一轮(12格),HourTick复位至0,一个时间轮完整周期完成.
%%% 2.事件原理说明:
%%% 2.1. 设置时间为TimeOut的事件时,根据TimeOut算出发生此事件时刻的指针位置{TriggerHour,TriggerMin,TriggerSec};
%%% 2.2. 用{TriggerHour,TriggerMin,TriggerSec}与当前指针{NowHour,NowMin,NowSec}对比得出事件存放在哪一个指针(Tick);
%%% 2.3. 所有层的指针每跳到下一格(Tick01)都会触发格子的事件列表,处理每一个事件Event01:
%%% 2.3.1 根据事件Event01的剩余TimeOut算出Event01应该存在上一层(跳得更快)层的位置Pos;
%%% 2.3.2 把事件更新到新的Pos(更新TimeOut);
%%% 2.3.3 重复处理完Tick01里面所有的事件;
%%% 2.3.4 清空Tick01的事件;
%%% 2.3.5 最底层(跳最快)层所有的事件遇到指针Tick都会立即执行;
%%% 3. Tip:
%%% 3.1. 在编译时使用c(mod_timeing_wheel,[{d,'DEBUGMODE'}]).打开debug开关
%%% 3.2. 所有的API只能在定时器所在进程调用
%%% @end
%%% Created : 25. Jun 2014 10:41 PM
%%%-------------------------------------------------------------------
-module(mod_timing_wheel).
%% API 所有的API只能在定时器所在进程调用
-export([
start/0,
pull_tick/0,
stop/0,
set_timer/4,
del_timer/1
]).
-export([
state/0,
func/2,
click_test/0,
start_click/1,
stop_click/0,
add_event/4,
batch_add_print/0,
del_timer_test/0,
print/1,
print_now_tick/0
]).
%% 三个时间轮序号:类似3维数组
-define(TIME_WHEEL1, 1).
-define(TIME_WHEEL2, 2).
-define(TIME_WHEEL3, 3).
-define(TWHEEL_LIST, [1, 2, 3]).%% 时间轮列表,三级
-define(MAX_WHEEL_NUM,3).%%(最高层)转得最慢时间轮序号
-define(INIT_TICK,0).%%开始时tick位置
%% 每个时间轮默认的槽数 可以根据需求调整,这里只是模拟时钟
-define(TIME_WHEEL1_SLOT_NUM, 60).%%256
-define(TIME_WHEEL2_SLOT_NUM, 60).%%64
-define(TIME_WHEEL3_SLOT_NUM, 12).%%64
-define(MAX_TIME_OUT, ?TIME_WHEEL1_SLOT_NUM*?TIME_WHEEL2_SLOT_NUM*?TIME_WHEEL3_SLOT_NUM).%% 支持的最大超时(S)
-define(SLOT, slot).
-define(CUR_SLOT, cur_slot).
%% 在编译时使用c(mod_timeing_wheel,[{d,'DEBUGMODE'}]).打开debug开关
-ifdef(DEBUGMODE).
-define(DEBUG(Format,ArgList), io:format(Format,ArgList)).
-else.
-define(DEBUG(Format,ArgList), ok).
-endif.
%%----------------------------------------------------------------------
%% API
%%----------------------------------------------------------------------
%% @doc 定时器初始化
start() ->
start(?TWHEEL_LIST).
stop() ->
stop(?TWHEEL_LIST).
%% @doc 最快指针(相当于时钟秒指针)触发的每一跳 inner tick
pull_tick() ->
CurSlot = get_cur_slot_by_wheel(?TIME_WHEEL1),
EventList = get_event_list_by_slot(?TIME_WHEEL1, CurSlot),
trigger_event(EventList),
%% 更新(转得最快)层slot的定时器列表
set_event_list_to_slot(?TIME_WHEEL1, CurSlot,[]),
pull_next_tick(?TIME_WHEEL1, CurSlot).
%% @doc 设置一个定时器 return
-spec set_timer(TimeOut, Module, Method, Args) -> {ok,Key}|{error,bad_time_out,TimeOut} when
TimeOut ::integer(),
Module :: atom(),
Method :: atom(),
Args :: list(),
Key :: erlang:make_ref().
set_timer(TimeOut, Module, Method, Args) when TimeOut < ?MAX_TIME_OUT andalso TimeOut> 0 ->
set_timer2(TimeOut, Module, Method, Args);
set_timer(TimeOut,_Module, _Method, _Args) ->
{error,bad_time_out,TimeOut}.
%% @doc 删除一个定时器
-spec del_timer(Key) -> ok|{error,not_exist} when
Key :: erlang:make_ref().%% 从set_timer的返回值得到
del_timer(Key) ->
case get_key_mapping(Key) of
{error,_Reason}=Err -> Err;
{TWheel,Slot} ->
EventList = get_event_list_by_slot(TWheel, Slot),
set_event_list_to_slot(TWheel, Slot, lists:keydelete(Key,1,EventList)),
ok
end.
%%----------------------------------------------------------------------
%% Internal Function
%%----------------------------------------------------------------------
start([]) ->
ok;
start([TWheel|Tail]) ->
init_twheel_slot(TWheel),
start(Tail).
stop([]) ->
ok;
stop([TWheel|Tail]) ->
erase_twheel_slot(TWheel),
stop(Tail).
%% @doc 初始化时间轮
init_twheel_slot(TWheelNum) ->
%% 初始化tick指针位置
set_cur_slot_by_wheel(TWheelNum, ?INIT_TICK),
init_twheel_slot(TWheelNum, get_max_slot_by_wheel(TWheelNum)).
init_twheel_slot(TWheel, ?INIT_TICK) ->
set_event_list_to_slot(TWheel, ?INIT_TICK, []);
init_twheel_slot(TWheel, Slot) ->
set_event_list_to_slot(TWheel, Slot-1, []),
init_twheel_slot(TWheel, Slot - 1).
erase_twheel_slot(TWheelNum) ->
erase_cur_slot_by_wheel(TWheelNum),
erase_twheel_slot(TWheelNum, get_max_slot_by_wheel(TWheelNum)).
erase_twheel_slot(TWheel, ?INIT_TICK) ->
erase_event_list_to_slot(TWheel, ?INIT_TICK);
erase_twheel_slot(TWheel, Slot) ->
erase_event_list_to_slot(TWheel, Slot-1),
erase_twheel_slot(TWheel, Slot - 1).
pull_next_tick(UpperTWheel, UpperCurSlot) ->
UpperMaxSlot = get_max_slot_by_wheel(UpperTWheel),
pull_next_tick2(UpperTWheel, UpperCurSlot, UpperMaxSlot).
%% 已到最高层的最后一个tick(相当于时钟已到00:59,下一跳就是00:00)
pull_next_tick2(?MAX_WHEEL_NUM, UpperCurSlot, UpperMaxSlot)when UpperCurSlot >= UpperMaxSlot-1 ->
set_cur_slot_by_wheel(?MAX_WHEEL_NUM,?INIT_TICK);
%% 上一层指针已跑满一轮(相当于时钟11:00:59 或11:59:00下一跳会使时钟next tick跳一下到:11:01:00或00:00:00)
pull_next_tick2(UpperTWheel, UpperCurSlot, UpperMaxSlot) when UpperCurSlot >= UpperMaxSlot-1 ->
%% 复位上一层时间轮指针
set_cur_slot_by_wheel(UpperTWheel, ?INIT_TICK),
%% 从这层时间轮取出定时器列表
CurTWheel = get_next_wheel(UpperTWheel),
CurSlotT = get_cur_slot_by_wheel(CurTWheel),
CurSlot = case CurSlotT >= get_max_slot_by_wheel(CurTWheel)-1 of
true -> ?INIT_TICK;
false -> CurSlotT +1
end,
EventList = get_event_list_by_slot(CurTWheel, CurSlot),
%% 更新这层slot的定时器
set_event_list_to_slot(CurTWheel, CurSlot, []),
%% 放入上一层的时间轮
?DEBUG("Tick:::::::EventList:~w~n",[EventList]),
handle_event_list(CurTWheel, EventList),
pull_next_tick(CurTWheel, CurSlotT);
%% 上一层没有跑完一轮
pull_next_tick2(UpperTWheel, UpperCurSlot, _UpperMaxSlot) ->
set_cur_slot_by_wheel(UpperTWheel, UpperCurSlot+1).
handle_event_list(_TWheel,[]) ->
ok;
handle_event_list(TWheel, EventList) ->
{NowTick,NowH,NowM,NowS} = get_all_time_tick(),
handle_event_list2(TWheel, EventList, NowTick, NowH, NowM, NowS).
handle_event_list2(TWheel, [{Key, TimeOut, Module, Method, Arg}|Tail], NowTick, NowH, NowM, NowS) ->
{NewTWheel, NewSlot, NewTimeOut} = get_slot_by_timeout(TimeOut,NowTick,NowH,NowM,NowS),
EventList = get_event_list_by_slot(NewTWheel, NewSlot),
set_event_list_to_slot(NewTWheel, NewSlot, [{Key,NewTimeOut, Module, Method, Arg}|EventList]),
update_key_mapping(Key,{NewTWheel,NewSlot}),
handle_event_list(TWheel, Tail).
%% 触发定时器事件
trigger_event([]) ->
ok;
trigger_event([{Key, _TimeOut, Module, Method, Arg}|Tail]) ->
try
erlang:apply(Module, Method, Arg)
catch _E:_Reason ->
io:format("trigger event error:~p,Reason:~p Module:~p Method:~p Arg:~p~n",[_E,_Reason,Module,Method,Arg])
end,
erase_key_mapping(Key),
trigger_event(Tail).
set_timer2(TimeOut, Module, Method, Arg) ->
{NowTick,NowH,NowM,NowS} = get_all_time_tick(),
{TWheel, Slot, TimeOut2} = get_slot_by_timeout(TimeOut,NowTick,NowH,NowM,NowS),
?DEBUG("Set timer:wheel~w:Slot:~w:TimeOut:~w~n",[TWheel, Slot, TimeOut2]),
EventList = get_event_list_by_slot(TWheel, Slot),
Key = erlang:make_ref(),
set_event_list_to_slot(TWheel, Slot, [{Key, TimeOut2, Module, Method, Arg}|EventList]),
update_key_mapping(Key,{TWheel,Slot}),
{ok,Key}.
%% return {WheelNum,slot,Timeout}
get_slot_by_timeout(0,_NowTick,_NowH,_NowM,_NowS) ->
{?TIME_WHEEL1, get_cur_slot_by_wheel(?TIME_WHEEL1), 0};
get_slot_by_timeout(TimeOut,NowTick,NowH,NowM,NowS) ->
TriggerTime = NowTick+TimeOut,
{TriggerH,TriggerM,TriggerS} = get_tick_pos(TriggerTime),
if TriggerTime >= ?MAX_TIME_OUT ->
{?TIME_WHEEL3, TriggerH, TriggerTime - ?MAX_TIME_OUT};
TriggerH =/= NowH ->
{?TIME_WHEEL3, TriggerH, cal_timeout_hour(TriggerM,TriggerS)};
TriggerM =/= NowM ->
{?TIME_WHEEL2, TriggerM, TriggerS};
TriggerS =/= NowS ->
{?TIME_WHEEL1, TriggerS, 0};
true ->
io:format("error:TimeOut~w:~w::~w:~n",[TimeOut,{NowH,NowM,NowS},{TriggerH,TriggerM,TriggerS}])
end.
cal_timeout_hour(TriggerM,TriggerS) ->
?DEBUG("Line:~w,cal_timeout_hour:~w~n",[?LINE,TriggerM*get_wheel_max_tick(?TIME_WHEEL1) + TriggerS]),
TriggerM*get_wheel_max_tick(?TIME_WHEEL1) + TriggerS.
%%通过总tick得到3个指针的位置
get_tick_pos(Tick) ->
SecPre = get_wheel_max_tick(?TIME_WHEEL2),
MinPre = get_wheel_max_tick(?TIME_WHEEL1),
Secs0 = Tick rem get_wheel_max_tick(?TIME_WHEEL3),
Hour = Secs0 div SecPre,
Secs1 = Secs0 rem SecPre,
Minute = Secs1 div MinPre,
Second = Secs1 rem MinPre,
{Hour, Minute, Second}.
%% return {NowTick总数,HourPos,MinPos,SecPos}
get_all_time_tick() ->
ThirdPos = get_cur_slot_by_wheel(?TIME_WHEEL3),
SecPos = get_cur_slot_by_wheel(?TIME_WHEEL2),
FirstPos = get_cur_slot_by_wheel(?TIME_WHEEL1),
{ThirdPos*get_wheel_max_tick(?TIME_WHEEL2)+
SecPos*get_wheel_max_tick(?TIME_WHEEL1)+
FirstPos, ThirdPos, SecPos, FirstPos}.
print_now_tick() ->
NowTick = {get_cur_slot_by_wheel(?TIME_WHEEL3),
get_cur_slot_by_wheel(?TIME_WHEEL2),
get_cur_slot_by_wheel(?TIME_WHEEL1)},
io:format("Tick:~w ~n",[NowTick]),
NowTick.
%% 获取下一级时间轮序号
get_next_wheel(WheelNum) ->
WheelNum+1.
%% 这一层时间轮最大的tick数
get_wheel_max_tick(TWheel) ->
get_wheel_max_tick(TWheel, 1).
get_wheel_max_tick(0, Tick) ->
Tick;
get_wheel_max_tick(TWheel, Tick) ->
get_wheel_max_tick(TWheel-1, Tick * get_max_slot_by_wheel(TWheel)).
%% 获取指定时间轮时间slot个数
get_max_slot_by_wheel(?TIME_WHEEL1) ->
?TIME_WHEEL1_SLOT_NUM ;
get_max_slot_by_wheel(?TIME_WHEEL2) ->
?TIME_WHEEL2_SLOT_NUM ;
get_max_slot_by_wheel(_) ->
?TIME_WHEEL3_SLOT_NUM .
%%----------------------------------------------------------------------
%% 进程字典相关 INTERVAL FUNC
%%----------------------------------------------------------------------
get_cur_slot_by_wheel(TWheelNum) ->
erlang:get({?MODULE, ?CUR_SLOT, TWheelNum}).
set_cur_slot_by_wheel(TWheel, Index) ->
erlang:put({?MODULE, ?CUR_SLOT, TWheel}, Index).
erase_cur_slot_by_wheel(TWheelNum) ->
erlang:erase({?MODULE, ?CUR_SLOT, TWheelNum}).
set_event_list_to_slot(TV, Slot, List) ->
erlang:put({?MODULE, ?SLOT, TV, Slot}, List).
get_event_list_by_slot(TWheelNum, Slot) ->
erlang:get({?MODULE, ?SLOT, TWheelNum, Slot}).
erase_event_list_to_slot(TWheel, Slot) ->
erlang:erase({?MODULE, ?SLOT, TWheel, Slot}).
update_key_mapping(Key,Pos) ->
erlang:put(Key,Pos).
get_key_mapping(Key) ->
case erlang:get(Key) of
undefined -> {error,not_exist};
Pos -> Pos
end.
erase_key_mapping(Key) ->
erlang:erase(Key).
%%----------------------------------------------------------------------
%% Only For Test
%%----------------------------------------------------------------------
click_test() ->
start_click(350),
timer:sleep(350*11),%%开始事件,让最后几个事件也加到此00:00:00hour里面测试
batch_add_print().
start_click(Tick) ->
erlang:register(?MODULE,erlang:spawn(fun() -> start_click2(Tick) end)).
stop_click() ->
erlang:send(?MODULE, stop).
add_event(TimeOut, Module, Method, Arg) ->
erlang:send(?MODULE,{add_event,[{TimeOut, Module, Method, Arg}]}).
batch_add_print() ->
EventList = [begin {TimeOut, ?MODULE, print, [TimeOut]} end||TimeOut<- lists:seq(1,?MAX_TIME_OUT)],
erlang:send(?MODULE,{add_event,EventList}).
start_click2(Tick) ->
start(),
erlang:send_after(Tick,self(),loop),
loop(Tick).
loop(Tick) ->
receive
loop ->
erlang:send_after(Tick,self(),loop),
pull_tick(),
loop(Tick);
{add_event,EventList} ->
[begin set_timer(TimeOut, Module, Method, Arg) end|| {TimeOut, Module, Method, Arg} <- EventList],
loop(Tick);
{func,From,Method,Arg} ->
Res = erlang:apply(?MODULE, Method, Arg),
erlang:send(From,Res),
loop(Tick);
stop ->
{stop,Tick}
end.
%% @doc 查定时器状态
state() ->
erlang:process_info(whereis(?MODULE)).
%% @doc 外部执行函数
func(Method,Arg) ->
erlang:send(?MODULE, {func, self(), Method, Arg}).
print(TimeOut) ->
io:format("TimeOutEvent:~w::",[TimeOut]),
print_now_tick().
%% only for test
del_timer_test() ->
{ok,Key} = set_timer(65, ?MODULE, print, ["zhongwencool"]),
{TWheel, Slot} = get_key_mapping(Key),
io:format("set_time:~w::EventList:~p ~n",[{TWheel, Slot},get_event_list_by_slot(TWheel, Slot)]),
Res = del_timer(Key),
io:format("del_time:Res~p::EventList:~p~n",[Res,get_event_list_by_slot(TWheel, Slot)]),
ok.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment