gen_server behaviour的误用

虽名为通用服务器,但gen_server的代码并不天生具有处理并发请求的能力,实际上一个gen_server写成的模块是运行在一个线程中的,所有针对它的请求都是序列化处理的,因此gen_server并不具有自动并行处理的能力,还得靠程序员自己实现并行。

熟悉OOP的程序员可能会误用gen_server,例如让一个gen_server的模块持有系统的配置信息,每当需要配置信息时就通过gen_server:call(或者cast)这个模块得到,但问题是如果大量的并行进程同时请求配置信息,这些并行请求就会在这里排队然后一个个的序列化调用从而阻塞原来本是并行的程序。

最简单的处理方法是处理逻辑另开一个线程执行,然后返回{noreply, State},例如:
handle_call(Req, From, State) ->
    Handler = State#state.store_handler,
    spawn(fun(Req) ->
        Res = ......, % 在此处理计算得到结果
        gen_server:reply(From, Res)  % 将结果返回给调用者
    end),
    {noreply, State}; % 直接返回


而调用者的执行方式不变:
gen_server:call(?SERVER, Req)
这是个同步调用,会等待在handle_call中新开的进程的计算结果返回后才继续执行下一步操作。

scalaris的设计中,配置信息由config模块管理,该模块类似于gen_server,在系统引导时会启动此服务模块进程,然后将服务器进程挂在boot_sup监督树上:监督树启动它时会spawn一个进程,这个进程首先创建一个受保护的ets命名表,随后把配置信息导入到这个表中,然后进入一个什么事都不做的“死循环”。创建ets表的进程是表的所有者,如果所有者进程死掉,表的内存空间就会被释放。 这个ets表就像一个黑板,任何人只有知道这个黑板的名字就可以阅读黑板的内容。看代码:
start_link(ConfigFiles) ->
    Owner = self(),
    Link = spawn_link(?MODULE, start, [ConfigFiles, Owner]),
    receive
	done ->
	    ok;
	X ->
	    io:format("unknown config message  ~p", [X])
    end,
    {ok, Link}. % Link代表的进程将被挂在监督树下

%@private
start([File], Owner) ->
    catch ets:new(config_ets, [set, protected, named_table]),
    populate_db(File),  % 从配置文件中读取配置信息并导入到ets表config_ets中
    Owner ! done,
    loop().    % 服务器进程进入死循环状态   

loop() ->
    receive
	_ ->
	    loop()
    end.


而中couch中,管理配置信息的模块也是采用ets保护表保存,但是一个gen_server behaviour的模块,获取配置时直接从命名表中读取配置信息,修改配置时就是通过gen_server:call进行设置。

你可能感兴趣的:(配置管理,Google,oop)