Erlang源码阅读笔记之proc_lib 下篇

in 编程
关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9

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

关注公众号【好便宜】( ID:haopianyi222 ),领红包啦~
阿里云,国内最大的云服务商,注册就送数千元优惠券:https://t.cn/AiQe5A0g
腾讯云,良心云,价格优惠: https://t.cn/AieHwwKl
搬瓦工,CN2 GIA 优质线路,搭梯子、海外建站推荐: https://t.cn/AieHwfX9
扫一扫关注公众号添加购物返利助手,领红包
Comments are closed.

推荐使用阿里云服务器

超多优惠券

服务器最低一折,一年不到100!

朕已阅去看看