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模块的全部内容了。