本文以作者提供的example.erl爲入口進行分析
地址 : https://github.com/devinus/poolboy
-module(example).
-behaviour(application).
-behaviour(supervisor).
-export([start/0, stop/0, squery/2, equery/3]).
-export([start/2, stop/1]).
-export([init/1]).
start() ->
application:start(?MODULE).
stop() ->
application:stop(?MODULE).
start(_Type, _Args) ->
supervisor:start_link({local, example_sup}, ?MODULE, []).
stop(_State) ->
ok.
init([]) ->
{ok, Pools} = application:get_env(example, pools),
PoolSpecs = lists:map(fun({Name, SizeArgs, WorkerArgs}) ->
PoolArgs = [{name, {local, Name}},
{worker_module, example_worker}] ++ SizeArgs,
poolboy:child_spec(Name, PoolArgs, WorkerArgs)
end, Pools),
{ok, {{one_for_one, 10, 10}, PoolSpecs}}.
squery(PoolName, Sql) ->
poolboy:transaction(PoolName, fun(Worker) ->
gen_server:call(Worker, {squery, Sql})
end).
equery(PoolName, Stmt, Params) ->
poolboy:transaction(PoolName, fun(Worker) ->
gen_server:call(Worker, {equery, Stmt, Params})
end).
poolboy初始化分析
1.在這個demo中,在init中通過 poolboy:child_spec(Name, PoolArgs, WorkerArgs) 調用poolboy:start_link/2
child_spec(PoolId, PoolArgs, WorkerArgs) ->
{PoolId, {poolboy, start_link, [PoolArgs, WorkerArgs]},
permanent, 5000, worker, [poolboy]}.
2. 在poolboy:start_link/2中進一步調用gen_server:start_link/3,4
start_link(PoolArgs, WorkerArgs) ->
start_pool(start_link, PoolArgs, WorkerArgs).
start_pool(StartFun, PoolArgs, WorkerArgs) ->
case proplists:get_value(name, PoolArgs) of
undefined ->
gen_server:StartFun(?MODULE, {PoolArgs, WorkerArgs}, []);
Name ->
gen_server:StartFun(Name, ?MODULE, {PoolArgs, WorkerArgs}, [])
end.
3. 接着調用poolboy:init/1,初始化state中的數據
init({PoolArgs, WorkerArgs}) ->
process_flag(trap_exit, true),
Waiting = queue:new(),
Monitors = ets:new(monitors, [private]),
init(PoolArgs, WorkerArgs, #state{waiting = Waiting, monitors = Monitors}).
%% worker_module:進程池中worker對應的模塊
init([{worker_module, Mod} | Rest], WorkerArgs, State) when is_atom(Mod) ->
%% 這裏的Sup就是池中所有worker的監督者
{ok, Sup} = poolboy_sup:start_link(Mod, WorkerArgs),
init(Rest, WorkerArgs, State#state{supervisor = Sup});
%% size: 翻譯作者原話大意是進程池的數量上限,個人理解爲連接池初始的worker數量
init([{size, Size} | Rest], WorkerArgs, State) when is_integer(Size) ->
init(Rest, WorkerArgs, State#state{size = Size});
%% max_overflow: 可新創建worker的最大數量
init([{max_overflow, MaxOverflow} | Rest], WorkerArgs, State) when is_integer(MaxOverflow) ->
init(Rest, WorkerArgs, State#state{max_overflow = MaxOverflow});
%% 策略: 分爲fifo先進先出和lifo後先進出兩種
init([{strategy, lifo} | Rest], WorkerArgs, State) ->
init(Rest, WorkerArgs, State#state{strategy = lifo});
init([{strategy, fifo} | Rest], WorkerArgs, State) ->
init(Rest, WorkerArgs, State#state{strategy = fifo});
init([_ | Rest], WorkerArgs, State) ->
init(Rest, WorkerArgs, State);
init([], _WorkerArgs, #state{size = Size, supervisor = Sup} = State) ->
%% 根據size的值,啓動對應數量的worker
Workers = prepopulate(Size, Sup),
{ok, State#state{workers = Workers}}.
自此,進程池啓動完成
poolboy調用分析
1.在上述的example中,調用poolboy有以下代碼
squery(PoolName, Sql) ->
poolboy:transaction(PoolName, fun(Worker) ->
gen_server:call(Worker, {squery, Sql})
end).
equery(PoolName, Stmt, Params) ->
poolboy:transaction(PoolName, fun(Worker) ->
gen_server:call(Worker, {equery, Stmt, Params})
end).
2.可以認爲核心方法是poolboy:transaction/2,3
這個函數的翻譯是"事務", 可以理解爲(checkout + checkin)的流程
transaction(Pool, Fun) ->
transaction(Pool, Fun, ?TIMEOUT).
%% 找出可用狀態的worker,調用傳入的Fun函數,返回結果,最後調用checkin釋放。
transaction(Pool, Fun, Timeout) ->
Worker = poolboy:checkout(Pool, true, Timeout),
try
Fun(Worker)
after
%% 重置worker的狀態
ok = poolboy:checkin(Pool, Worker)
end.
3.poolboy:checkout/3
這個函數主要作用是取出1個可用閒的worker,參數中Block的含義爲"是否阻塞調用"。
checkout(Pool, Block, Timeout) ->
%% 返回唯一引用,防止ets插入相同內容導致覆蓋
CRef = make_ref(),
try
%% 嘗試返回一個worker
gen_server:call(Pool, {checkout, CRef, Block}, Timeout)
catch
?EXCEPTION(Class, Reason, Stacktrace) ->
%% 超時/異常處理,取消等待
gen_server:cast(Pool, {cancel_waiting, CRef}),
erlang:raise(Class, Reason, ?GET_STACK(Stacktrace))
end.
%% 嘗試返回一個可用worker
handle_call({checkout, CRef, Block}, {FromPid, _} = From, State) ->
#state{supervisor = Sup,
workers = Workers,
monitors = Monitors,
overflow = Overflow,
max_overflow = MaxOverflow} = State,
case Workers of
%% 有空閒的worker,直接返回
[Pid | Left] ->
MRef = erlang:monitor(process, FromPid),
true = ets:insert(Monitors, {Pid, CRef, MRef}),
{reply, Pid, State#state{workers = Left}};
%% 沒有空閒的worker,但worker數量尚未達到上限,繼續啓動
[] when MaxOverflow > 0, Overflow < MaxOverflow ->
{Pid, MRef} = new_worker(Sup, FromPid),
true = ets:insert(Monitors, {Pid, CRef, MRef}),
{reply, Pid, State#state{overflow = Overflow + 1}};
%% 沒有空閒worker,並且worker數量達到上限,對於非阻塞調用,直接返回原子full
[] when Block =:= false ->
{reply, full, State};
%% 沒有空閒worker,並且worker數量達到上限,對於阻塞調用,加入waiting隊列,並進行等待
[] ->
MRef = erlang:monitor(process, FromPid),
Waiting = queue:in({From, CRef, MRef}, State#state.waiting),
{noreply, State#state{waiting = Waiting}}
end;
%% 取消等待
handle_cast({cancel_waiting, CRef}, State) ->
case ets:match(State#state.monitors, {'$1', CRef, '$2'}) of
[[Pid, MRef]] ->
demonitor(MRef, [flush]),
true = ets:delete(State#state.monitors, Pid),
NewState = handle_checkin(Pid, State),
{noreply, NewState};
[] ->
Cancel = fun({_, Ref, MRef}) when Ref =:= CRef ->
demonitor(MRef, [flush]),
false;
(_) ->
true
end,
Waiting = queue:filter(Cancel, State#state.waiting),
{noreply, State#state{waiting = Waiting}}
end;
4.poolboy:checkin/2
釋放一個使用狀態worker
checkin(Pool, Worker) when is_pid(Worker) ->
gen_server:cast(Pool, {checkin, Worker}).
handle_cast({checkin, Pid}, State = #state{monitors = Monitors}) ->
case ets:lookup(Monitors, Pid) of
[{Pid, _, MRef}] ->
%% 取消監視器
true = erlang:demonitor(MRef),
true = ets:delete(Monitors, Pid),
NewState = handle_checkin(Pid, State),
{noreply, NewState};
[] ->
{noreply, State}
end;
%% 處理空閒的worker
handle_checkin(Pid, State) ->
#state{supervisor = Sup,
waiting = Waiting,
monitors = Monitors,
overflow = Overflow,
strategy = Strategy} = State,
case queue:out(Waiting) of
%% 隊列不爲空
{{value, {From, CRef, MRef}}, Left} ->
true = ets:insert(Monitors, {Pid, CRef, MRef}),
%% 向等待隊列首部的進程發送一個worker的Pid
gen_server:reply(From, Pid),
State#state{waiting = Left};
%% 隊列爲空且worker的數量大於size時(overflow > 0)
{empty, Empty} when Overflow > 0 ->
%% 關閉額外開啓的worker
ok = dismiss_worker(Sup, Pid),
State#state{waiting = Empty, overflow = Overflow - 1};
% 隊列爲空
{empty, Empty} ->
%% 根據不同的策略將空閒下來worker放進首部or尾部
Workers = case Strategy of
lifo -> [Pid | State#state.workers];
fifo -> State#state.workers ++ [Pid]
end,
State#state{workers = Workers, waiting = Empty, overflow = 0}
end.
關於worker關閉與用戶進程關閉的處理
1.worker關閉時的處理
handle_info({'EXIT', Pid, _Reason}, State) ->
#state{supervisor = Sup,
monitors = Monitors} = State,
case ets:lookup(Monitors, Pid) of
[{Pid, _, MRef}] ->
true = erlang:demonitor(MRef),
true = ets:delete(Monitors, Pid),
NewState = handle_worker_exit(Pid, State),
{noreply, NewState};
[] ->
case lists:member(Pid, State#state.workers) of
true ->
W = lists:filter(fun (P) -> P =/= Pid end, State#state.workers),
{noreply, State#state{workers = [new_worker(Sup) | W]}};
false ->
{noreply, State}
end
end;
%% worker關閉的處理,總的來說,只要不超overflow,都會啓動新的worker
handle_worker_exit(Pid, State) ->
#state{supervisor = Sup,
monitors = Monitors,
overflow = Overflow} = State,
case queue:out(State#state.waiting) of
{{value, {From, CRef, MRef}}, LeftWaiting} ->
NewWorker = new_worker(State#state.supervisor),
true = ets:insert(Monitors, {NewWorker, CRef, MRef}),
gen_server:reply(From, NewWorker),
State#state{waiting = LeftWaiting};
{empty, Empty} when Overflow > 0 ->
State#state{overflow = Overflow - 1, waiting = Empty};
{empty, Empty} ->
Workers =
[new_worker(Sup)
| lists:filter(fun (P) -> P =/= Pid end, State#state.workers)],
State#state{workers = Workers, waiting = Empty}
end.
2.監視器檢測到用戶進程關閉時的處理
handle_info({'DOWN', MRef, _, _, _}, State) ->
case ets:match(State#state.monitors, {'$1', '_', MRef}) of
%% 已經開始執行任務
[[Pid]] ->
true = ets:delete(State#state.monitors, Pid),
NewState = handle_checkin(Pid, State),
{noreply, NewState};
%% 還在等待隊列
[] ->
Waiting = queue:filter(fun ({_, _, R}) -> R =/= MRef end, State#state.waiting),
{noreply, State#state{waiting = Waiting}}
end;
總結
poolboy用不到400行的代碼實行了一個簡單易懂的高性能進程池,代碼十分值得一讀。