Erlang源码阅读笔记之proc_lib 下篇

hibernate组

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

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

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
.

输出:app

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设计原则"。atom

init_ack组

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

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公开出来的,咱们本身能够在须要的场景去包装它。hibernate

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模块的所有内容了。

相关文章
相关标签/搜索