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模塊的所有內容了。

相關文章
相關標籤/搜索