Erlang源码阅读笔记之proc_lib 下篇


声明:本文转载自https://my.oschina.net/u/3745448/blog/1588629,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

hibernate组

hibernate组只有一个实现,即hibernate/3,但是在搞明白proc_lib的hibernate实现细节之前,需要先弄清楚erlang:hibernate/3的运行机制。

erlang:hibernate/3会使当前进程立即陷入到waiting状态,并即刻进行垃圾回收,只有当进程接收到消息的时候才会从waiting状态恢复,并从指定的回调函数开始运行,之前的进行栈信息,hibernate会全部丢弃。写个测试代码:

test_hibernate() ->     Pid = spawn(         fun() ->             receive                 Msg -> io:format("Before hibernate, I received msg: ~p~n", [Msg])             end,              try erlang:hibernate(?MODULE, a, [])             catch                 Type:Reason -> io:format("Catch exception: ~p:~p~n", [Type, Reason])             end         end     ),     io:format("before hibernate, heap size: ~p~n", [process_info(Pid, total_heap_size)]),     Pid ! "hi",     timer:sleep(1000),     io:format("in hibernate, heap size: ~p~n", [process_info(Pid, total_heap_size)]),     Pid ! 1,     timer:sleep(1000),     io:format("after weakup, heap size: ~p~n", [process_info(Pid, total_heap_size)]),     Pid ! 0,     timer:sleep(1000),     io:format("after exception happend, heap size: ~p~n", [process_info(Pid, total_heap_size)]).  a() ->      receive         Msg ->              io:format("I received in hibernate callback ~p~n", [Msg]),             1 / Msg,             a()     end . 

输出:

13> c(test_link). test_link.erl:49: Warning: the result of the expression is ignored (suppress the warning by assigning the expression to the _ variable) {ok,test_link} 14> test_link:test_hibernate(). before hibernate, heap size: {total_heap_size,233} Before hibernate, I received msg: "hi" in hibernate, heap size: {total_heap_size,1} I received in hibernate callback 1 after weakup, heap size: {total_heap_size,233} I received in hibernate callback 0  =ERROR REPORT==== 12-Dec-2017::16:20:06 === Error in process <0.84.0> with exit value: {badarith,[{test_link,a,0,[{file,"test_link.erl"},{line,49}]}]} after exception happend, heap size: undefined ok 

可以看到在进程执行hibernate之前,占据的堆空间为233个字,但是在hibernate之后,只占了1个字,当有消息进入进程邮箱时,进程会解除hibernate的状态,占据的堆空间大小会重新恢复,但因为丢弃了之前的栈信息,异常捕获不会起作用(而这也是proc_lib:hibernate/3所要解决的问题)。

可以想象,当我们有大量进程长期处于waiting状态,需要等待某个时间点被唤醒时,通过hibernate节省的内存开销是相当可观的,但hibernate也有个问题,就是栈信息会被丢弃,前面我们已经看到,proc_lib都是通过exit_p来统一处理错误的,但如果在目标函数里面调用了erlang:hibernate,那么异常就不会再通过exit_p来处理,造成失控,因此这就是proc_lib也封装了一个hibernate函数的原因。

hibernate(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->     erlang:hibernate(?MODULE, wake_up, [M, F, A]). 

重点是wake_up函数的实现:

wake_up(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->     try 	apply(M, F, A)      catch 	Class:Reason -> 	    exit_p(Class, Reason, erlang:get_stacktrace())     end. 

再这里面,我们又重新通过try catch以及exit_p来处理了一下异常。这样就能确保hibernate回调的函数也能符合"OTP设计原则"。

init_ack组

前面我们说start函数时讲过,spawn新进程后,当前进程会等待新进程返回的信息,其中一种就是ack,用于通知当前监控进程自己工作完成,init_ack就是封装了ack消息的发送,实现非常的简单:

init_ack(Parent, Return) ->     Parent ! {ack, self(), Return},     ok.  init_ack(Return) ->     [Parent|_] = get('$ancestors'),     init_ack(Parent, Return). 

init_p 组

我们前面在讲述spawn和start组的时候已经讲述了init_p的作用,不再赘述。但proc_lib是把init_p作为API公开出来的,我们自己可以在需要的场景去包装它。

format 组

format组的函数用于将CrashReport格式化成字符串,可以选择不同的编码以及栈深度,略。

stop组

stop组提供了两个函数,stop/1和stop/3:

stop(Process) ->     stop(Process, normal, infinity).  stop(Process, Reason, Timeout) ->     {Pid, Mref} = erlang:spawn_monitor(do_stop(Process, Reason)),     receive 	{'DOWN', Mref, _, _, Reason} -> 	    ok; 	{'DOWN', Mref, _, _, {noproc,{sys,terminate,_}}} -> 	    exit(noproc); 	{'DOWN', Mref, _, _, CrashReason} -> 	    exit(CrashReason)     after Timeout -> 	    exit(Pid, kill), 	    receive 		{'DOWN', Mref, _, _, _} -> 		    exit(timeout) 	    end     end.  do_stop(Process, Reason) ->     fun() -> 	    Mref = erlang:monitor(process, Process), 	    ok = sys:terminate(Process, Reason, infinity), 	    receive 		{'DOWN', Mref, _, _, ExitReason} -> 		    exit(ExitReason) 	    end     end. 

看上去挺绕的,我们先在shell中测试一下stop的行为:

15> 15> Pid = spawn(fun() -> receive Msg -> io:format("I received msg: ~p~n", [Msg]) end end). <0.86.0> 16> proc_lib:stop(Pid). I received msg: {system,{<0.88.0>,#Ref<0.0.3.69>},{terminate,normal}} ** exception exit: {normal,{sys,terminate,[<0.86.0>,normal,infinity]}}      in function  proc_lib:stop/3 (proc_lib.erl, line 796) 

通过stop/1,目标进程收到了terminate然后抛出一个exit异常。

18> proc_lib:stop(Pid, hehe, 10). ** exception exit: noproc      in function  proc_lib:stop/3 (proc_lib.erl, line 794) 

Pid已经不存在了,抛出一个noproc异常。

17> Pid2 = spawn(fun() -> receive Msg -> io:format("I received msg: ~p~n", [Msg]) end end). <0.91.0> 19> proc_lib:stop(Pid2, hehe, 10). I received msg: {system,{<0.96.0>,#Ref<0.0.5.355>},{terminate,hehe}} ** exception exit: {normal,{sys,terminate,[<0.91.0>,hehe,infinity]}}      in function  proc_lib:stop/3 (proc_lib.erl, line 796) 

再看下超时:

25> Pid5 = spawn(fun() -> receive Msg -> io:format("I received msg: ~p~n", [Msg]), timer:sleep(100000), io:format("finis hed ~n~p") end end). <0.113.0> 26> 26> proc_lib:stop(Pid5, hehe, 5000). I received msg: {system,{<0.115.0>,#Ref<0.0.5.404>},{terminate,hehe}} ** exception exit: timeout      in function  proc_lib:stop/3 (proc_lib.erl, line 801) 

进程收到了消息,5秒钟后会抛出一个timeout异常,同时目标进程Pid5也会被干掉,后面的"finished"没有打印出来。

总之,stop组的函数对进程提供了一种更好的终结方式,可以更灵活的定义终结消息,当然目标进程也要接收约定,同时也提供了超时机制,让进程在无法响应外界请求时强制kill掉。

嗯,以上就是proc_lib模块的全部内容了。

本文发表于2017年12月12日 18:33
(c)注:本文转载自https://my.oschina.net/u/3745448/blog/1588629,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2016 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1