Joe Armstrong:
許多用戶常常使用gen_server
似乎認為它能夠解決所有事情蛇耀,并且嘗試強制他們的問題適用于gen_server
,盡管gen_server
不適合他們的問題课幕。
gen_server
是一個非常簡單的代碼,可以很容易的改變它五垮,以適用于其他問題乍惊,然而人們并不常常這樣做。
在這篇文章中放仗,我將介紹gen_server
的工作原理润绎。
為了說明這一點,我寫了mini_gs.erl
诞挨,如果你能明白mini_gs
莉撇,這是一個迷你gen_server
。你會明白98%的gen_server
真實工作情況亭姥,真正的gen_server
只是在mini_gs
的基礎(chǔ)上添加了一些華而不實的東西稼钩。
mini_gs.erl
具有與gen_server.erl
的兼容接口(gen_server
API的一個子集)
-module(mini_gs).
-export([start_link/4, call/2]).
%% this module behaves just like the gen-server for a sub-set of the gen_server
%% commands
start_link({local,Name}, Mod, Args, _Opts) ->
register(Name, spawn(fun() -> start(Mod, Args) end)).
call(Name, X) ->
Name ! {self(), Ref = make_ref(), X},
receive
{Ref, Reply} -> Reply
end.
start(Mod, Args) ->
{ok, State} = Mod:init(Args),
loop(Mod, State).
loop(Mod, State) ->
receive
{From, Tag, X} ->
case Mod:handle_call(X, From, State) of
{reply, R, State1} ->
From ! {Tag, R},
loop(Mod, State1)
end
end.
這里沒有那么麻煩,客戶端通過調(diào)用mini_gs:start_link({local,Name},Mod,Args,Opts)
開始
我忽略了mini_gs
中的選項达罗,也固定了server的名稱格式{local,Name}
(gen_server
對于server的名稱有更多的一般參數(shù))
接下來什么發(fā)生了
mini_gs
調(diào)用了Mod:init(Args)
用來初始化server坝撑,這要求返回{ok,State}
,
并且State成為服務(wù)的初始狀態(tài)。
現(xiàn)在mini_gs
調(diào)用 loop(Mod,State)
粮揉。當mini_gs
收到{From,Tag,X}
消息巡李,會調(diào)用Mod:handle_call(X,From,State ),
這要求返回{reply,R,State1}
扶认。R1會返回給客戶端侨拦,服務(wù)器以新的狀態(tài)State1
調(diào)用loop/2
。
僅此而已辐宾,call/2
是一個接口程序狱从,用來抽象出客戶端和服務(wù)端之間的接口膨蛮。
現(xiàn)在我們可以編寫一個簡單的客戶端應(yīng)用。
-module(kv).
%% These define the client API
-export([start/0, store/2,lookup/1]).
%% these must be defined because they are called by gs
-export([init/1, handle_call/3]).
-define(GS, mini_gs).
%% -define(GS, gen_server).
%% define the client API
start() -> ?GS:start_link({local,someatom}, kv, foo, []).
store(Key,Val) -> ?GS:call(someatom, {putval,Key,Val}).
lookup(Key) -> ?GS:call(someatom, {getval,Key}).
%% define the internal routines
init(foo) -> {ok, dict:new()}.
handle_call({putval, Key, Val}, _From, Dict) ->
{reply, ok, dict:store(Key, Val, Dict)};
handle_call({getval,Key}, _From, Dict) ->
{reply, dict:find(Key, Dict), Dict}.
該模塊可以調(diào)用gen_server
或mini_gs
(只需要修改define聲明)
所以現(xiàn)在我們已經(jīng)把一個單一鍵值存儲進程(使用dict)轉(zhuǎn)換成了全局key-value
存儲季研。請注意kv.erl
并沒有使用原語spawn_link
發(fā)送敞葛、接收、或在此基礎(chǔ)上与涡。即kv.erl
是用純序列代碼編寫的惹谐。
這是為什么我們做了gen_server
抽象。你可以編寫良好的順序代碼(handle_call
和init
函數(shù))進行參數(shù)化并發(fā)行為驼卖,即你完成工作卻并不需要了解并發(fā)性氨肌。我們已經(jīng)"抽象出"了并發(fā)性。
當你完全不理解抽象部分的時候酌畜,事情變得有問題怎囚。也許抽象的不適合你的需要。我曾經(jīng)看到很多這樣的例子檩奠,其中gen_server
是不合適的桩了。關(guān)鍵性的測試使gen_server
代碼看起來顯示格式意大利面條一樣雜亂無序附帽,如果是這樣的埠戳,那么所有你做的是使應(yīng)用成為不適當?shù)男问剑癫缓线m的鞋碼一樣蹩腳蕉扮,你應(yīng)該挖掘gen_server
定制為你自己的整胃。
參考:Joe amstrong的mail
鏈接:找回自己