并不是简单的通过spawn在子进程启动函数中启动一个进程然后返回{ok, Pid}就可以让子进程拥有出错自动重启的功能。实际上,需要使用proc_lib:spawn_link或者proc_lib:start_link启动子进程,才能在子进程出错退出时让supervisor自动重启它
Erlang实践心得-01
by达达 转载自http://1234n.com/?post/qou3eb
正文中红色字体为本人笔记。
之前用OTP的时候不求甚解只求能用,所以对OTP的特性和内部机制缺乏深入的了解。这几天重构架构的代码,针对性的进行了一些实验,在这里分享一些心得体会。
supervisor的子进程
一开始使用supervisor的时候,我用的是init/1返回子进程规格列表的方式,并且所有子进程只有两种类型,一种是supervisor进程,一种是gen_server。
但这次代码重构中,我遇到一个情况。如果我可以启动普通的进程而不是gen_server,我就可以把一些我觉得没必要做成gen_server的模块代码精简掉。因为一些模块代码完全没用到任何gen_server的优势,只是借gen_server来做为supervisor的子进程启动。于是我便开始实验如何在supervisor中启动普通的进程。
通过实验,我发现我原先对supervisor的理解完全是没有根据的猜测,果然实践才是检验真理的唯一标准。
首先,我原以为supervisor的子进程规格中提供的Module、Function、Arguments就是子进程的入口,supervisor会通过它来自己启动子进程。但实际上,子进程规格提供的是子进程的启动函数入口,supervisor通过调用这个入口函数来启动子进程,而这个函数通过返回{ok, Pid}来告诉supervisor子进程创建成功。
其次,并不是简单的通过spawn在子进程启动函数中启动一个进程然后返回{ok, Pid}就可以让子进程拥有出错自动重启的功能。实际上,需要使用proc_lib:spawn_link或者proc_lib:start_link启动子进程,才能在子进程出错退出时让supervisor自动重启它。
上面的第二点,我是通过阅读gen_server的代码才了解到的,因为一开始我用来做实验的代码没有用spawn(因为第一点的错误理解),但这个问题很容易就发现了,接着我用了spawn看似正确启动了子进程,但实际上这些子进程一旦出错退出就不会再被启动。我换gen_server实验了一遍,确信用gen_server是可以重启的。于是我便阅读gen_server:start_link的代码一探究竟,从源码中可以看到gen_server模块调用gen模块来启动进程,而gen模块最终通过proc_lib:start_link来启动进程。
proc_lib:start_link和proc_lib:spawn_link的不同之处在于,前者是同步创建子进程,后者是异步创建子进程,proc_lib:start_link调用后会阻塞,直到子进程初始化完毕,调用proc_lib:init_ack后才返回。而proc_lib:spawn_link一调用就会立即返回子进程ID。
调试信息和错误信息
之前一直傻傻的不懂得在erlang进程启动时加上-boot start_sasl,搞得很怕在supervisor启动子进程时出错,因为一旦出错就只能看到很简单的一行错误提示,完全看不到堆栈跟踪信息。但实际上只要在启动erlang时加上-boot start_sasl,就可以看到更多提示信息提示了。
项目中进程会输出一些文字信息来帮助调试程序,通常就是在需要的地方写上io:format,但是这些提示信息的输出在实际生产环境下是不需要的,而且也会带来对于的性能损耗。重构代码时我定义了一组宏来代替io:format输出文字信息,代码如下:
-ifdef(debug).
-define(LOG(Msg, List), io:format(Msg, List)).
-else.
-define(LOG(Msg, List), ok).
-endif.
这样只需在Emakefile里面添加或删除{d, debug},就可以编译出针对性不同的代码了。