Erlang入门(四)—— 错误处理和鲁棒性

 闲话不提,继续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 }) .

当没有使用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
.
再看看结果,
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
->
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);
->
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

四、运行时失败
    一个运行时错误将导致进程的非正常终止,伴随着非正常终止EXIT信号将发出给所有连接的进程,EXIT信号中有Reason并且Reason中包含一个atom类型用于说明错误的原因,常见的原因如下:

badmatch - 匹配失败,比如一个进程进行1=3的匹配,这个进程将终止,并发出{'EXIT', From, badmatch}信号给连接的进程

badarg  - 顾名思义,参数错误,比如atom_to_list(123),数字不是atom,因此将发出{'EXIT', From, badarg}信号给连接进程

case_clause - 缺少分支匹配,比如
   
=   3 ,
case M of
  1   ->
    yes;
  2   ->
    no
end
.
没有分支3,因此将发出{'EXIT', From, case_clause}给连接进程

if_clause - 同理,if语句缺少匹配分支

function_clause - 缺少匹配的函数,比如:
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)将重新开启默认行为。
   例子:
- 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信号,这个程序是很简单了,测试看看:
>  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节的例子完整写了下,并添加客户端进程用于测试:
- 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
.
    

回家了,有空再详细说明下这个例子吧。执行:
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 >
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值