名为my_bank的银行模块
-behaviour(gen_server).
-export([start/0]).
%% gen_server回调函数
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-compile(export_all).
-define(SERVER, ?MODULE).
start() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
stop() -> gen_server:call(?MODULE, stop).
new_account(Who) -> gen_server:call(?MODULE, {new, Who}).
deposit(Who, Amount) -> gen_server:call(?MODULE, {add, Who, Amount}).
withdraw(Who, Amount) -> gen_server:call(?MODULE, {remove, Who, Amount}).
init([]) -> {ok, ets:new(?MODULE, [])}.
handle_call({new, Who}, _From, Tab) ->
Reply = case ets:lookup(Tab, Who) of
[] -> ets:insert(Tab, {Who, 0}),
{welcome, Who};
[_] -> {Who, you_already_are_a_customer}
end,
{reply, Reply, Tab};
handle_call({add, Who, X}, _From, Tab) ->
Reply = case ets:lookup(Tab, Who) of
[] -> not_a_customer;
[{Who, Balance}] ->
NewBalance = Balance + X,
ets:insert(Tab, {Who, NewBalance}),
{ok, Who, your_balance_is, NewBalance}
end,
{reply, Reply, Tab};
handle_call({remove, Who, X}, _From, Tab) ->
Reply = case ets:lookup(Tab, Who) of
[] -> not_a_customer;
[{Who, Balance}] when X =< Balance ->
NewBalance = Balance - X,
ets:insert(Tab, {Who, NewBalance}),
{thanks, Who, your_balance_is, NewBalance};
[{Who, Balance}] ->
{none, Who, you_only_have, Balance, in_the_bank}
end,
{reply, Reply, Tab};
handle_call(stop, _From, Tab) ->
{stop, normal, stopped, Tab}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
从这个简单的模板,我们能够清楚的看到代码的整体结构
只介绍了最简单的gen_server使用方式,但它应该足以应付大多数需求了。
复杂程度更高的应用程序经常会让gen_server回复一个noreply返回值,并把真正的回复任务委派给另一个进程。
把服务器行为抽象成两个部分:通用部分可以用于所有服务器,特有部分(处理模块)可以用来对通用部分进行定制。
这么做的优点是代码能整齐地一分为二。通用部分解决众多并发和错误处理问题,而处理模块只包含顺序代码。
练习
任务中心(job_center)持有一个必须完成的任务队列,这些任务会被编号,任何人都能向队列添加任务。工人可以从队列请求任务,并告诉任务中心已经执行了某项任务。任务是由fun表示的,要执行任务F,工人必须执行F()函数。 (1) 实现任务中心的基本功能,它的接口如下。
1.job_centre:start_link() -> true 启动任务中心服务器。
2. job_centre:add_job(F) -> JobNumber 添加任务F到任务队列,然后返回一个整数任务编号。
3.job_centre:work_wanted() -> {JobNumber,F} | no 请求任务。如果工人想要一个任务,就调用job_centre:work_wanted()。如果队列里 有任务,就会返回一个{JobNumber, F}元组。工人执行F()来完成任务。如果队列里没 有任务,则会返回no。请确保同一项任务每次只分配给一个工人,并确保系统是公平的, 意思是任务按照请求的顺序进行分配。
4.job_centre:job_done(JobNumber) 发出任务完成的信号。如果工人完成了某一项任务,就必须调用job_centre:job_done (JobNumber)。
5. 添加一个名为job_centre:statistics()的统计函数,让它报告队列内、进行中和已完 成任务的状态。
-behaviour(gen_server).
-export([test/0, start_link/0, stop/0, init/1, add_job/1, work_wanted/0, job_done/1, handle_call/3, handle_cast/2, handle_info/2, job_statistic/0]).
test() ->
start_link(),
add_job(fun() -> good_job end).
%% 启动
start_link() ->
gen_server:start_link({global, ?MODULE}, ?MODULE, [], []).
%% 停止
stop() ->
gen_server:call(?MODULE, stop).
%% 添加任务F到任务队列
add_job(F) ->
gen_server:call(?MODULE, {add_job,F}).
%% 创建第一个键,JobNumber为0,因为它并不是任务
init([]) ->
State = ets:new(?MODULE, []),
ets:insert(State, {job_index, 0}),
{ok, State}.
%% 请求任务
work_wanted() ->
gen_server:call(?MODULE, work_wanted).
%% 完成任务
job_done(JobNumber) ->
gen_server:call(?MODULE, {job_done, JobNumber}).
%% 查询当前任务中心所有任务状态
job_statistic() ->
gen_server:call(?MODULE, job_statistic).
.
%% 增加,JobNumber+1,状态为等待
handle_call({add_job, Fun}, _From, State) ->
[{job_index, JobNumber}] = ets:lookup(State, job_index),
ets:insert(State, [{job_index, JobNumber + 1}, {JobNumber, Fun, wait}]),
{reply, JobNumber, State};
%% 查询当前任务中心所有任务状态
handle_call(job_statistic, _From, State) ->
TaskWait = ets:match(State, {'$1', '$2', wait}),
TaskDoing = ets:match(State, {'$1', '$2', doing}),
TaskDone = ets:match(State, {'$1', '$2', done}),
{reply, {length(TaskWait), length(TaskDoing), length(TaskDone)}, State};
handle_call(work_wanted, {_Pid, _Ref}, State) ->
case ets:match(State, {'$1', '$2', wait}, 1) of
'$end_of_table' ->
{reply, no, State};
{[[JobNumber, Fun]], _} ->
ets:update_element(State, JobNumber, {3, doing}),
{reply, {JobNumber, Fun}, State}
end;
handle_call({job_done, JobNumber, Status}, _From, State) ->
case ets:lookup_element(State, JobNumber, 3) of
done ->
Reply = done;
doing ->
Reply = ok,
ets:update_element(State, JobNumber, {3, Status});
wait ->
Reply = err
end,
{reply, Reply, State};
handle_call(stop, _From, State) ->
{stop, normal, stop, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.