Erlang源码阅读笔记之gen_server进程启动
2017-12-15 00:00
232 查看
概述
gen_server大概是用的最为广泛的OTP组件了,它提供了通用服务进程的生命周期骨架(进程启动、事件循环、消息处理以及终止)和协作API,并通过behaviour规定了各个周期回调函数的形态,只要我们按照gen_server behaviour实现回调函数,就能将其纳入到OTP的服务管理体系当中。gen_server文档中API与各回调函数的对应关系:
gen_server module api | callback function |
---|---|
gen_server:start | Module:init/1 |
gen_server:start_link | Module:init/1 |
gen_server:stop | Module:terminate/2 |
gen_server:call | Module:handle_call/3 |
gen_server:multi_call | Module:handle_call/3 |
gen_server:cast | Module:handle_cast/2 |
gen_server:abcast | Module:handle_cast/2 |
- | Module:handle_info/2 |
- | Module:terminate/2 |
- | Module:code_change/3 |
进程启动
可以看到,gen_server在对外API中提供了start(start/3,start/4)和start_link(start_link/3, start_link/4)两组函数,start和start_link的最大区别在于,start启动的是一个单独的进程,不会加入到任何OTP监控树中,而start_link是可以被加入到监控树中的(当然得由Supervisor去启动才可以,两者建立link关系)。平时用的最多的会是start_link,而start基本场景是对某个模块进行单独的测试。启动函数的实现:
start(Mod, Args, Options) -> gen:start(?MODULE, nolink, Mod, Args, Options). start(Name, Mod, Args, Options) -> gen:start(?MODULE, nolink, Name, Mod, Args, Options). start_link(Mod, Args, Options) -> gen:start(?MODULE, link, Mod, Args, Options). start_link(Name, Mod, Args, Options) -> gen:start(?MODULE, link, Name, Mod, Args, Options).
可以看到四个函数都是将请求转发到了gen模块的start函数,而且start和start_link的差别在于第二个参数,start是nolink,start_link是link,再看看gen模块做了什么:
start(GenMod, LinkP, Name, Mod, Args, Options) -> case where(Name) of undefined -> do_spawn(GenMod, LinkP, Name, Mod, Args, Options); Pid -> {error, {already_started, Pid}} end. start(GenMod, LinkP, Mod, Args, Options) -> do_spawn(GenMod, LinkP, Mod, Args, Options). do_spawn(GenMod, link, Mod, Args, Options) -> Time = timeout(Options), proc_lib:start_link(?MODULE, init_it, [GenMod, self(), self(), Mod, Args, Options], Time, spawn_opts(Options)); do_spawn(GenMod, _, Mod, Args, Options) -> Time = timeout(Options), proc_lib:start(?MODULE, init_it, [GenMod, self(), self, Mod, Args, Options], Time, spawn_opts(Options)). do_spawn(GenMod, link, Name, Mod, Args, Options) -> Time = timeout(Options), proc_lib:start_link(?MODULE, init_it, [GenMod, self(), self(), Name, Mod, Args, Options], Time, spawn_opts(Options)); do_spawn(GenMod, _, Name, Mod, Args, Options) -> Time = timeout(Options), proc_lib:start(?MODULE, init_it, [GenMod, self(), self, Name, Mod, Args, Options], Time, spawn_opts(Options)). ... timeout(Options) -> case lists:keyfind(timeout, 1, Options) of {_,Time} -> Time; false -> infinity end.
两个start的区别在于一个有Name,一个没有,如果指定了Name,会通过where函数检查进程是否已经注册了:
where({global, Name}) -> global:whereis_name(Name); where({via, Module, Name}) -> Module:whereis_name(Name); where({local, Name}) -> whereis(Name).
where会分三种情况去查询,global、via和local,global和local很好理解,{via, Module, Name}这个会是用在啥场景?思考了半天,觉着可能是这样,有时候可能global并不能满足我们的要求,比如可能是因为性能问题或者应用需要有自定义的namespace规则,这个时候我们就需要自己来实现服务发现逻辑了(比如通过zookeeper或者etcd),那么此时就可以通过这种via的方式,定义一个像global那样提供服务查询接口以的模块来进行查找,这块儿有OO语言里面Ducking Type的影子,哈哈。
然后link跟no_link的差别在于link是通过proc_lib:start_link创建的进程,而no_link是通过proc_lib:start创建的进程。这俩函数之前已经看过,都是通过同步的方式来创建新进程,只不过start_link建立的是link关系(这也是通过这种方式能加入OTP监控树的原因),而start建立的是monitor关系。
另外可以看到,我们在这里能够通过Option参数来控制新建进程的生存时间,lists:keyfind(Key, N, TupleList)是一个很有意思的函数,它检查一个由元组所构成的列表TupleList,如果其中一个元组的第N个元素为Key,那么就会返回,如果找不到会返回false,这个在解析配置时会很方便:
1> L = [{a, 1}, {b, 2}, {c, 3}]. [{a,1},{b,2},{c,3}] 2> lists:keyfind(b, 1, L). {b,2}
接下来重点看新建进程逻辑init_it做了什么了,gen模块里面的init_it实现:
init_it(GenMod, Starter, Parent, Mod, Args, Options) -> init_it2(GenMod, Starter, Parent, self(), Mod, Args, Options). init_it(GenMod, Starter, Parent, Name, Mod, Args, Options) -> case register_name(Name) of true -> init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options); {false, Pid} -> proc_lib:init_ack(Starter, {error, {already_started, Pid}}) end. init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options) -> GenMod:init_it(Starter, Parent, Name, Mod, Args, Options). ... register_name({local, Name} = LN) -> try register(Name, self()) of true -> true catch error:_ -> {false, where(LN)} end; register_name({global, Name} = GN) -> case global:register_name(Name, self()) of yes -> true; no -> {false, where(GN)} end; register_name({via, Module, Name} = GN) -> case Module:register_name(Name, self()) of yes -> true; no -> {false, where(GN)} end.
发现gen模块的init_it就做了一点微小的事情:没有指定服务进程名称,甩锅给GenMod(在这里也就是gen_server)的init_it;如果指定了服务进程名,则对服务进行注册并检查之前是否注册过,注册过直接终止,没注册过则甩锅给gen_server的init。在这里我们可以看见gen模块进程创建的一个逻辑,如果我们为进程指定了Name,那么在相应的范围(local、via、global)只能创建一个进程,如果不指定Name,那么进程创建数目会不受限制。
突然感觉这里跟Spring的Bean scope很像,比如spring中的Bean可以是application范围单例的(就相当于这里的global),也可以是用户会话级别的,也可以是请求级别的,或者是prototype,每次用到都创建最新的实例,还可以自定义bean scope,有意思!
既然gen模块都最终把锅甩给了gen_server的init_it,那我们再回头看看gen_server中init_it的实现:
init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name0, Mod, Args, Options) -> Name = gen:name(Name0), Debug = gen:debug_options(Name, Options), HibernateAfterTimeout = gen:hibernate_after(Options), case init_it(Mod, Args) of {ok, {ok, State}} -> proc_lib:init_ack(Starter, {ok, self()}), loop(Parent, Name, State, Mod, infinity, HibernateAfterTimeout, Debug); {ok, {ok, State, Timeout}} -> proc_lib:init_ack(Starter, {ok, self()}), loop(Parent, Name, State, Mod, Timeout, HibernateAfterTimeout, Debug); {ok, {stop, Reason}} -> gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); {ok, ignore} -> gen:unregister_name(Name0), proc_lib:init_ack(Starter, ignore), exit(normal); {ok, Else} -> Error = {bad_return_value, Else}, proc_lib:init_ack(Starter, {error, Error}), exit(Error); {'EXIT', Class, Reason, Stacktrace} -> gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, terminate_reason(Class, Reason, Stacktrace)}), erlang:raise(Class, Reason, Stacktrace) end. init_it(Mod, Args) -> try {ok, Mod:init(Args)} catch throw:R -> {ok, R}; Class:R -> {'EXIT', Class, R, erlang:get_stacktrace()} end.
总体上看来,就是回调业务模块的init函数,等待其返回结果并进行判断,决定下一步的走向。如果init返回正常,就会向父进程发送ack消息(父进程会结束阻塞,也就是说,在init执行完毕前,父进程一直是阻塞的),然后以带超时和不带超时两种不同的方式进入到事件循环,如果是其它情况,则会取消注册,向父进程发送ack消息,然后按情况抛出不同的异常。
相关文章推荐
- erlang中启动一个gen_server和一个纯粹进程的区别
- Tomcat源码阅读之Server.xml文件的处理与Catalina启动流程
- Netty源码阅读(一) ServerBootstrap启动
- [erlang 学习] 转载的 gen_server中管理新的进程
- Android M 启动源码分析笔记之 - Init 进程
- Openstack学习笔记之——Neutron-server服务加载与启动源码分析(三)
- Android M 启动源码分析笔记之 - Init 进程
- System Server进程启动过程源码分析
- [Chrome源码阅读] 理解Chrome导航网址的流程及render进程启动模式
- Tomcat源码阅读之Server.xml文件的处理与Catalina启动流程
- [Erlang 0036] "HOW TO"不创建崩溃报告主动销毁gen_server进程
- [Erlang 学习笔记]erlang behaviour小结之gen_server
- Android源码(2) --- SystemServer进程启动流程
- MongoDB源码阅读之Shard源码分析--CongfigServer启动
- 源码分析Android SystemServer进程的启动过程
- Netty源码阅读(一) ServerBootstrap启动
- System Server进程启动过程源码分析
- Netty源码阅读(一) ServerBootstrap启动
- nginx 源码分析阅读笔记-进程管理
- Android系统源码阅读(3):子Activity在进程内的启动过程