mini gen_server(它是怎么工作的)

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的基础上添加了一些华而不实的东西。

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.

这里没有那么麻烦,客户端通过调用mini_gs:start_link({local,Name},Mod,Args,Opts)开始

我忽略了mini_gs中的选项,也固定了server的名称格式{local,Name}gen_server对于server的名称有更多的一般参数)

接下来什么发生了

mini_gs调用了Mod:init(Args)用来初始化server,这要求返回{ok,State},
并且State成为服务的初始状态。

现在mini_gs调用 loop(Mod,State) 。当mini_gs
收到{From,Tag,X}消息,会调用Mod:handle_call(X,From,State ),这要求返回{reply,R,State1}。R1会返回给客户端,服务器以新的状态State1调用loop/2

仅此而已,call/2是一个接口程序,用来抽象出客户端和服务端之间的接口。

现在我们可以编写一个简单的客户端应用。

-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}.

该模块可以调用gen_servermini_gs(只需要修改define声明)

所以现在我们已经把一个单一键值存储进程(使用dict)转换成了全局key-value存储。请注意kv.erl并没有使用原语spawn_link发送、接收、或在此基础上。即kv.erl是用纯序列代码编写的。

这是为什么我们做了gen_server抽象。你可以编写良好的顺序代码(handle_callinit函数)进行参数化并发行为,即你完成工作却并不需要了解并发性。我们已经"抽象出"了并发性。

当你完全不理解抽象部分的时候,事情变得有问题。也许抽象的不适合你的需要。我曾经看到很多这样的例子,其中gen_server是不合适的。关键性的测试使gen_server代码看起来显示格式意大利面条一样杂乱无序,如果是这样的,那么所有你做的是使应用成为不适当的形式,像不合适的鞋码一样蹩脚,你应该挖掘gen_server定制为你自己的。

参考:Joe amstrong的mail
链接:找回自己

你可能感兴趣的:(mini gen_server(它是怎么工作的))