任何一門語言都有自己的錯誤處理機制,Erlang也不例外,語法錯誤編譯器可以幫你指出,而邏輯錯誤和運行時錯誤就只有靠程序員利用Erlang提供的機制來妥善處理,放置程序的崩潰。
Erlang的機制有:
1)監控某個表達式的執行
2)監控其他進程的行為
3)捕捉未定義函數執行錯誤等
一、catch和throw語句 調用某個會產生錯誤的表達式會導致調用進程的非正常退出,比如錯誤的模式匹配(2=3),這種情況下可以用catch語句:
catch expression 試看一個例子,一個函數foo:
java 代碼
- foo(1) ->
- hello;
- foo(2) ->
- throw({myerror, abc});
- foo(3) ->
- tuple_to_list(a);
- foo(4) ->
- exit({myExit, 222}).
當沒有使用catch的時候,假設有一個標識符為Pid的進程調用函數foo(在一個模塊中),那么:
foo(1) - 返回hello
foo(2) - 語句throw({myerror, abc})執行,因為我們沒有在一個catch中調用foo(2),因此進程Pid將因為錯誤而終止。
foo(3) - tuple_to_list將一個元組轉化為列表,因為a不是元組,因此進程Pid同樣因為錯誤而終止
foo(4) - 因為沒有使用catch,因此foo(4)調用了exit函數將使進程Pid終止,{myExit, 222} 參數用于說明退出的原因。
foo(5) - 進程Pid將因為foo(5)的調用而終止,因為沒有和foo(5)匹配的函數foo/1。
讓我們看看用catch之后是什么樣:
java 代碼
- demo(X) ->
- case catch foo(X) of
- {myerror, Args} ->
- {user_error, Args};
- {'EXIT', What} ->
- {caught_error, What};
- Other ->
- Other
- end.
再看看結果,
demo(1) - 沒有錯誤發生,因此catch語句將返回表達式結果hello
demo(2) - foo(2)拋出錯誤{myerror, abc},被catch返回,因此將返回{user_error,abc}
demo(3) - foo(3)執行失敗,因為參數錯誤,因此catch返回{'EXIT',badarg'},最后返回{caught_error,badarg}
demo(4) - 返回{caught_error,{myexit,222}}
demo(5) - 返回{caught_error,function_clause}
使用catch和throw可以將可能產生錯誤的代碼包裝起來,throw可以用于尾遞歸的退出等等。Erlang是和scheme一樣進行尾遞歸優化的,它們都沒有顯式的迭代結構(比如for循環)
二、進程的終止 在進程中調用exit的BIFs就可以顯式地終止進程,exit(normal)表示正常終止,exit(Reason)通過Reason給出非正常終止的原因。進程的終止也完全有可能是因為運行時錯誤引起的。
三、連接的進程 進程之間的連接是雙向的,也就是說進程A打開一個連接到B,也意味著有一個從B到A的連接。當進程終止的時候,有一個EXIT信號將發給所有與它連接的進程。信號的格式如下:
{'EXIT', Exiting_Process_Id, Reason}
Exiting_Process_Id 是指終止的進程標記符
Reason 是進程終止的原因。如果Reason是normal,接受這個信號的進程的默認行為是忽略這個信號。默認對Exit信號的處理可以被重寫,以允許進程對Exit信號的接受做出不同的反應。
1.連接進程:
通過link(Pid),就可以在調用進程與進程Pid之間建立連接
2.取消連接
反之通過unlink(Pid)取消連接。
3.創立進程并連接:
通過spawn_link(Module, Function, ArgumentList)創建進程并連接,該方法返回新創建的進程Pid
通過進程的相互連接,許多的進程可以組織成一個網狀結構,EXIT信號(非normal)從某個進程發出(該進程終止),所有與它相連的進程以及與這些進 程相連的其他進程,都將收到這個信號并終止,除非它們實現了自定義的EXIT信號處理方法。一個進程鏈狀結構的例子:
java 代碼
- -module(normal).
- -export([start/1, p1/1, test/1]).
- start(N) ->
- register(start, spawn_link(normal, p1, [N - 1])).
- p1(0) ->
- top1();
- p1(N) ->
- top(spawn_link(normal, p1, [N - 1]),N).
- top(Next, N) ->
- receive
- X ->
- Next ! X,
- io:format("Process ~w received ~w~n", [N,X]),
- top(Next,N)
- end.
- top1() ->
- receive
- stop ->
- io:format("Last process now exiting ~n", []),
- exit(finished);
- X ->
- io:format("Last process received ~w~n", [X]),
- top1()
- end.
- test(Mess) ->
- start ! Mess.
執行:
java 代碼
- > normal:start(3).
- true
- > normal:test(123).
- Process 2 received 123
- Process 1 received 123
- Last process received 123
-
- > normal:test(stop).
- Process 2 received stop
- Process 1 received stop
- Last process now exiting
- stop
四、運行時失敗 一個運行時錯誤將導致進程的非正常終止,伴隨著非正常終止EXIT信號將發出給所有連接的進程,EXIT信號中有Reason并且Reason中包含一個atom類型用于說明錯誤的原因,常見的原因如下:
badmatch - 匹配失敗,比如一個進程進行1=3的匹配,這個進程將終止,并發出{'EXIT', From, badmatch}信號給連接的進程
badarg - 顧名思義,參數錯誤,比如atom_to_list(123),數字不是atom,因此將發出{'EXIT', From, badarg}信號給連接進程
case_clause - 缺少分支匹配,比如
java 代碼
- M = 3,
- case M of
- 1 ->
- yes;
- 2 ->
- no
- end.
沒有分支3,因此將發出{'EXIT', From, case_clause}給連接進程
if_clause - 同理,if語句缺少匹配分支
function_clause - 缺少匹配的函數,比如:
java 代碼
- foo(1) ->
- yes;
- foo(2) ->
- no.
如果我們調用foo(3),因為沒有匹配的函數,將發出{'EXIT', From, function_clause} 給連接的進程。
undef - 進程執行一個不存在的函數
badarith - 非法的算術運算,比如1+foo。
timeout_value - 非法的超時時間設置,必須是整數或者infinity
nocatch - 使用了throw,沒有相應的catch去通訊。
五、修改默認的信號接收action 當進程接收到EXIT信號,你可以通過process_flag/2方法來修改默認的接收行為。執行process_flag(trap_exit, true)設置捕獲EXIT信號為真來改變默認行為,也就是將EXIT信號作為一般的進程間通信的信號進行接受并處理;process_flag (trap_exit,false)將重新開啟默認行為。
例子:
java 代碼
- -module(link_demo).
- -export([start/0, demo/0, demonstrate_normal/0, demonstrate_exit/1,
- demonstrate_error/0, demonstrate_message/1]).
- start() ->
- register(demo, spawn(link_demo, demo, [])).
- demo() ->
- process_flag(trap_exit, true),
- demo1().
- demo1() ->
- receive
- {'EXIT', From, normal} ->
- io:format("Demo process received normal exit from ~w~n",[From]),
- demo1();
- {'EXIT', From, Reason} ->
- io:format("Demo process received exit signal ~w from ~w~n",[Reason, From]),
- demo1();
- finished_demo ->
- io:format("Demo finished ~n", []);
- Other ->
- io:format("Demo process message ~w~n", [Other]),
- demo1()
- end.
- demonstrate_normal() ->
- link(whereis(demo)).
- demonstrate_exit(What) ->
- link(whereis(demo)),
- exit(What).
- demonstrate_message(What) ->
- demo ! What.
- demonstrate_error() ->
- link(whereis(demo)),
- 1 = 2.
-
創建的進程執行demo方法,demo方法中設置了trap_exit為true,因此,在receive中可以像對待一般的信息一樣處理EXIT信號,這個程序是很簡單了,測試看看:
java 代碼
- > link_demo:start().
- true
- > link_demo:demonstrate_normal().
- true
- Demo process received normal exit from <0.13.1>
- > link_demo:demonstrate_exit(hello).
- Demo process received exit signal hello from <0.14.1>
- ** exited: hello **
-
- > link_demo:demonstrate_exit(normal).
- Demo process received normal exit from <0.13.1>
- ** exited: normal **
-
- > link_demo:demonstrate_error().
- !!! Error in process <0.17.1> in function
- !!! link_demo:demonstrate_error()
- !!! reason badmatch
- ** exited: badmatch **
- Demo process received exit signal badmatch from <0.17.1>
六、未定義函數和未注冊名字1.當調用一個未定義的函數時,Mod:Func(Arg0,...,ArgN),這個調用將被轉為:
error_handler:undefined_function(Mod, Func, [Arg0,...,ArgN])
其中的error_handler模塊是系統自帶的錯誤處理模塊
2.當給一個未注冊的進程名發送消息時,調用將被轉為:
error_handler:unregistered_name(Name,Pid,Message)
3.如果不使用系統自帶的error_handler,可以通過process_flag(error_handler, MyMod) 設置自己的錯誤處理模塊。
七、Catch Vs. Trapping Exits這兩者的區別在于應用場景不同,Trapping Exits應用于當接收到其他進程發送的EXIT信號時,而catch僅用于表達式的執行。
第8章介紹了如何利用錯誤處理機制去構造一個健壯的系統,用了幾個例子,我將8.2節的例子完整寫了下,并添加客戶端進程用于測試:
java 代碼
- -module(allocator).
- -export([start/1,server/2,allocate/0,free/1,start_client/0,loop/0]).
- start(Resources) ->
- Pid = spawn(allocator, server, [Resources,[]]),
- register(resource_alloc, Pid).
- %函數接口
- allocate() ->
- request(alloc).
- free(Resource) ->
- request({free,Resource}).
- request(Request) ->
- resource_alloc ! {self(),Request},
- receive
- {resource_alloc, error} ->
- exit(bad_allocation); % exit added here
- {resource_alloc, Reply} ->
- Reply
- end.
- % The server.
- server(Free, Allocated) ->
- process_flag(trap_exit, true),
- receive
- {From,alloc} ->
- allocate(Free, Allocated, From);
- {From,{free,R}} ->
- free(Free, Allocated, From, R);
- {'EXIT', From, _ } ->
- check(Free, Allocated, From)
- end.
- allocate([R|Free], Allocated, From) ->
- link(From),
- io:format("連接客戶端進程~w~n",[From]),
- From ! {resource_alloc,{yes,R}},
- server(Free, [{R,From}|Allocated]);
- allocate([], Allocated, From) ->
- From ! {resource_alloc,no},
- server([], Allocated).
- free(Free, Allocated, From, R) ->
- case lists:member({R,From}, Allocated) of
- true ->
- From ! {resource_alloc,ok},
- Allocated1 = lists:delete({R, From}, Allocated),
- case lists:keysearch(From,2,Allocated1) of
- false->
- unlink(From),
- io:format("從進程~w斷開~n",[From]);
- _->
- true
- end,
- server([R|Free],Allocated1);
- false ->
- From ! {resource_alloc,error},
- server(Free, Allocated)
- end.
-
- check(Free, Allocated, From) ->
- case lists:keysearch(From, 2, Allocated) of
- false ->
- server(Free, Allocated);
- {value, {R, From}} ->
- check([R|Free],
- lists:delete({R, From}, Allocated), From)
- end.
- start_client()->
- Pid2=spawn(allocator,loop,[]),
- register(client, Pid2).
- loop()->
- receive
- allocate->
- allocate(),
- loop();
- {free,Resource}->
- free(Resource),
- loop();
- stop->
- true;
- _->
- loop()
- end.
-
回家了,有空再詳細說明下這個例子吧。執行:
java 代碼
- 1> c(allocator).
- {ok,allocator}
- 2> allocator:start([1,2,3,4,5,6]).
- true
- 3> allocator:start_client().
- true
- 4> client!allocate
- .
- allocate連接客戶端進程<0.37.0>
-
- 5> client!allocate.
- allocate連接客戶端進程<0.37.0>
-
- 6> client!allocate.
- allocate連接客戶端進程<0.37.0>
-
- 7> allocator:allocate().
- 連接客戶端進程<0.28.0>
- {yes,4}
- 8> client!{free,1}.
- {free,1}
- 9> client!{free,2}.
- {free,2}
- 10> client!allocate.
- allocate連接客戶端進程<0.37.0>
-
- 11> client!allocate.
- allocate連接客戶端進程<0.37.0>
-
- 12> client!stop.
- stop
- 13> allocator:allocate().
- 連接客戶端進程<0.28.0>
- {yes,3}
- 14> allocator:allocate().
- 連接客戶端進程<0.28.0>
- {yes,2}
- 15> allocator:allocate().
- 連接客戶端進程<0.28.0>
- {yes,1}
- 16>
posted on 2009-09-11 10:13
暗夜教父 閱讀(323)
評論(0) 編輯 收藏 引用 所屬分類:
erlang