Created
October 6, 2018 05:34
-
-
Save tianchaijz/576f20663e8d99f99893a111b71a2dc6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Many users seem to use the gen_server for absolutely everything | |
and often force their problems to fit the gen_sever even though the | |
gen_server is not appropriate for their problems. | |
The gen_server is an extremely simple bit of code | |
which can be easily changed to fit other problems, though people don't | |
often do this. | |
In this posting I'll explain the basic idea of how the gen_server works. | |
To illustrate this I've written mini_gs.erl - this is a mini gen_server | |
if you understand mini_gs.erl you'll have understood 98% of how the real | |
gen_server works. The real gen_server just adds a load of "bells and | |
whistles" | |
to mini_gs.erl. | |
mini_gs.erl has a compatible interface to gen_server.erl (for a subset of | |
the 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. | |
There. That wasn't so painful. The client starts by calling | |
min_gs:start_link({local,Name}, Mod, Args, Opts) | |
I've ignored Opts in mini_gs, also frozen the name of the server to be of | |
the form | |
{local, Name} (gen_server has more general arguments for the name of the | |
server) | |
What happens? | |
mini_gs calls Mod:init(Args) to initialize the server, this must return {ok, | |
State} | |
and State becomes the initial state of the server. | |
Now mini_gs calls loop(Mod, State.) When mini_gs receives a message {From, | |
Tag, X} | |
it calls Mod:handle_call(X, From, State) which returns {repy, R, State1}. R1 | |
is | |
sent back to the client, and the server calls loop/2 with the new state | |
State1. | |
That's it. call/2 is an interface routine to abstract out the interface | |
between the | |
client and the sever. | |
Now we can write a simple client application. | |
-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}. | |
This module can call either gen_server or mini_gs (just change the define | |
statement) | |
So now we have turned a single process key-value store (using dict) into a | |
global | |
key-value store. Note that kv.erl never uses the primitives spawn_link, | |
send, receive | |
or do on. ie kv.erl is written with pure *sequential* code. | |
This is *why* we made the gen_server abstraction. You can write well-typed | |
sequential code (the handle_call and init functions) to parametrize | |
a concurrent behavior, ie you need to know nothing about concurrency to | |
get the job done. We've "abstracted out" the concurrency. | |
Things become problematic when you do not entirely understand the | |
abstraction. | |
Maybe the abstraction is inappropriate for your needs. I have seen many | |
examples | |
of code where the gen_server *is* inappropriate. The acid test is "does the | |
gen_sever code look like spaghetti" if the answer is yes then all you have | |
done | |
is shoe horn the applications into an inappropriate form. In this case | |
you should ditch the gen_server and roll-your own. | |
/Joe |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment