去了趟福州,事情没搞定,托给同学帮忙处理了,回家休息了两天就来上班了。回家这几天最大的收获是第四次重读《深入Java虚拟机》,以前不大明了的章节豁然开朗,有种开窍的感觉,水到渠成,看来技术的学习还是急不来。
闲话不提,继续Erlang的学习,上次学习到分布式编程的章节,剩下三章分别是错误处理、构造健壮的系统和杂项,错误处理和构造健壮的系统今天一起读了,仅摘记下。
任何一门语言都有自己的错误处理机制,Erlang也不例外,语法错误编译器可以帮你指出,而逻辑错误和运行时错误就只有靠程序员利用Erlang提供的机制来妥善处理,放置程序的崩溃。
Erlang的机制有:
1)监控某个表达式的执行
2)监控其他进程的行为
3)捕捉未定义函数执行错误等
一、catch和throw语句
调用某个会产生错误的表达式会导致调用进程的非正常退出,比如错误的模式匹配(2=3),这种情况下可以用catch语句:
当没有使用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之后是什么样:
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信号处理方法。一个进程链状结构的例子:
执行:
四、运行时失败
一个运行时错误将导致进程的非正常终止,伴随着非正常终止EXIT信号将发出给所有连接的进程,EXIT信号中有Reason并且Reason中包含一个atom类型用于说明错误的原因,常见的原因如下:
badmatch - 匹配失败,比如一个进程进行1=3的匹配,这个进程将终止,并发出{'EXIT', From, badmatch}信号给连接的进程
badarg - 顾名思义,参数错误,比如atom_to_list(123),数字不是atom,因此将发出{'EXIT', From, badarg}信号给连接进程
case_clause - 缺少分支匹配,比如
if_clause - 同理,if语句缺少匹配分支
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)将重新开启默认行为。
例子:
创建的进程执行demo方法,demo方法中设置了trap_exit为true,因此,在receive中可以像对待一般的信息一样处理EXIT信号,这个程序是很简单了,测试看看:
六、未定义函数和未注册名字
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节的例子完整写了下,并添加客户端进程用于测试:
回家了,有空再详细说明下这个例子吧。执行:
闲话不提,继续Erlang的学习,上次学习到分布式编程的章节,剩下三章分别是错误处理、构造健壮的系统和杂项,错误处理和构造健壮的系统今天一起读了,仅摘记下。
任何一门语言都有自己的错误处理机制,Erlang也不例外,语法错误编译器可以帮你指出,而逻辑错误和运行时错误就只有靠程序员利用Erlang提供的机制来妥善处理,放置程序的崩溃。
Erlang的机制有:
1)监控某个表达式的执行
2)监控其他进程的行为
3)捕捉未定义函数执行错误等
一、catch和throw语句
调用某个会产生错误的表达式会导致调用进程的非正常退出,比如错误的模式匹配(2=3),这种情况下可以用catch语句:
catch expression
试看一个例子,一个函数foo:
foo(
1
)
->
hello;
foo( 2 ) ->
throw({myerror , abc});
foo( 3 ) ->
tuple_to_list(a);
foo( 4 ) ->
exit ({myExit , 222 }) .
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之后是什么样:
demo(X)
->
case catch foo(X) of
{myerror , Args} ->
{user_error , Args};
{ ' EXIT ' , What} ->
{caught_error , What};
Other ->
Other
end .
再看看结果,
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信号处理方法。一个进程链状结构的例子:
-
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 .
- 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 .
执行:
>
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
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 - 缺少分支匹配,比如
M
=
3
,
case M of
1 ->
yes;
2 ->
no
end .
没有分支3,因此将发出{'EXIT', From, case_clause}给连接进程
case M of
1 ->
yes;
2 ->
no
end .
if_clause - 同理,if语句缺少匹配分支
function_clause - 缺少匹配的函数,比如:
foo(
1
)
->
yes;
foo( 2 ) ->
no .
如果我们调用foo(3),因为没有匹配的函数,将发出{'EXIT', From, function_clause} 给连接的进程。
yes;
foo( 2 ) ->
no .
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)将重新开启默认行为。
例子:
-
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 .
- 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信号,这个程序是很简单了,测试看看:
>
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 >
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节的例子完整写了下,并添加客户端进程用于测试:
-
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 .
- 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 .
回家了,有空再详细说明下这个例子吧。执行:
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 >
{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 >