`

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

阅读更多
    去了趟福州,事情没搞定,托给同学帮忙处理了,回家休息了两天就来上班了。回家这几天最大的收获是第四次重读《深入Java虚拟机》,以前不大明了的章节豁然开朗,有种开窍的感觉,水到渠成,看来技术的学习还是急不来。
    闲话不提,继续Erlang的学习,上次学习到分布式编程的章节,剩下三章分别是错误处理、构造健壮的系统和杂项,错误处理和构造健壮的系统今天一起读了,仅摘记下。
    任何一门语言都有自己的错误处理机制,Erlang也不例外,语法错误编译器可以帮你指出,而逻辑错误和运行时错误就只有靠程序员利用Erlang提供的机制来妥善处理,放置程序的崩溃。
    Erlang的机制有:
1)监控某个表达式的执行
2)监控其他进程的行为
3)捕捉未定义函数执行错误等

一、catch和throw语句
    调用某个会产生错误的表达式会导致调用进程的非正常退出,比如错误的模式匹配(2=3),这种情况下可以用catch语句:
                                      catch expression
    试看一个例子,一个函数foo:

java 代码
 
  1. foo(1) ->  
  2. hello;  
  3. foo(2) ->  
  4. throw({myerror, abc});  
  5. foo(3) ->  
  6. tuple_to_list(a);  
  7. foo(4) ->  
  8. 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 代码
 
  1. demo(X) ->  
  2. case catch foo(X) of  
  3.   {myerror, Args} ->  
  4.        {user_error, Args};  
  5.   {'EXIT', What} ->  
  6.        {caught_error, What};  
  7.   Other ->  
  8.        Other  
  9. 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 代码
 
  1. -module(normal).  
  2. -export([start/1, p1/1, test/1]).  
  3. start(N) ->  
  4. register(start, spawn_link(normal, p1, [N - 1])).  
  5.  p1(0) ->  
  6.    top1();  
  7.  p1(N) ->  
  8.    top(spawn_link(normal, p1, [N - 1]),N).  
  9. top(Next, N) ->  
  10. receive  
  11. X ->  
  12. Next ! X,  
  13. io:format("Process ~w received ~w~n", [N,X]),  
  14. top(Next,N)  
  15. end.  
  16. top1() ->  
  17. receive  
  18. stop ->  
  19. io:format("Last process now exiting ~n", []),  
  20. exit(finished);  
  21. X ->  
  22. io:format("Last process received ~w~n", [X]),  
  23. top1()  
  24. end.  
  25. test(Mess) ->  
  26. start ! Mess.  

执行:
java 代码
 
  1. > normal:start(3).  
  2. true  
  3. > normal:test(123).  
  4. Process 2 received 123  
  5. Process 1 received 123  
  6. Last process received 123  
  7.   
  8. > normal:test(stop).  
  9. Process 2 received stop  
  10. Process 1 received stop  
  11. Last process now exiting  
  12. stop  

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

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

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

case_clause - 缺少分支匹配,比如
   
java 代码
 
  1. M = 3,  
  2. case M of  
  3.   1 ->  
  4.     yes;  
  5.   2 ->  
  6.     no  
  7. end.  

没有分支3,因此将发出{'EXIT', From, case_clause}给连接进程

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

function_clause - 缺少匹配的函数,比如:
java 代码
 
  1. foo(1) ->  
  2.   yes;  
  3. foo(2) ->  
  4.   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 代码
 
  1. -module(link_demo).  
  2. -export([start/0, demo/0, demonstrate_normal/0, demonstrate_exit/1,  
  3. demonstrate_error/0, demonstrate_message/1]).  
  4. start() ->  
  5.   register(demo, spawn(link_demo, demo, [])).  
  6. demo() ->  
  7.   process_flag(trap_exit, true),  
  8. demo1().  
  9.   demo1() ->  
  10.   receive  
  11.     {'EXIT', From, normal} ->  
  12.       io:format("Demo process received normal exit from ~w~n",[From]),  
  13.      demo1();  
  14.     {'EXIT', From, Reason} ->  
  15.       io:format("Demo process received exit signal ~w from ~w~n",[Reason, From]),  
  16.      demo1();  
  17.     finished_demo ->  
  18.       io:format("Demo finished ~n", []);  
  19.     Other ->  
  20.       io:format("Demo process message ~w~n", [Other]),  
  21.      demo1()  
  22.   end.  
  23. demonstrate_normal() ->  
  24.   link(whereis(demo)).  
  25. demonstrate_exit(What) ->  
  26.   link(whereis(demo)),  
  27.   exit(What).  
  28. demonstrate_message(What) ->  
  29.   demo ! What.  
  30. demonstrate_error() ->  
  31.   link(whereis(demo)),  
  32.   1 = 2.  
  33.    

    创建的进程执行demo方法,demo方法中设置了trap_exit为true,因此,在receive中可以像对待一般的信息一样处理EXIT信号,这个程序是很简单了,测试看看:
java 代码
 
  1. > link_demo:start().  
  2. true  
  3. > link_demo:demonstrate_normal().  
  4. true  
  5. Demo process received normal exit from <0.13.1>  
  6. > link_demo:demonstrate_exit(hello).  
  7. Demo process received exit signal hello from <0.14.1>  
  8. ** exited: hello **  
  9.   
  10. > link_demo:demonstrate_exit(normal).  
  11. Demo process received normal exit from <0.13.1>  
  12. ** exited: normal **  
  13.   
  14. > link_demo:demonstrate_error().  
  15. !!! Error in process <0.17.1> in function  
  16. !!! link_demo:demonstrate_error()  
  17. !!! reason badmatch  
  18. ** exited: badmatch **  
  19. 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 代码
 
  1. -module(allocator).  
  2. -export([start/1,server/2,allocate/0,free/1,start_client/0,loop/0]).  
  3. start(Resources) ->  
  4.    Pid = spawn(allocator, server, [Resources,[]]),  
  5. register(resource_alloc, Pid).  
  6. %函数接口  
  7. allocate() ->  
  8.    request(alloc).  
  9. free(Resource) ->  
  10.   request({free,Resource}).  
  11. request(Request) ->  
  12.   resource_alloc ! {self(),Request},  
  13.   receive  
  14.     {resource_alloc, error} ->  
  15.       exit(bad_allocation); % exit added here  
  16.     {resource_alloc, Reply} ->  
  17.       Reply  
  18.  end.  
  19. % The server.  
  20. server(Free, Allocated) ->  
  21.  process_flag(trap_exit, true),  
  22.  receive  
  23.    {From,alloc} ->  
  24.          allocate(Free, Allocated, From);  
  25.    {From,{free,R}} ->  
  26.         free(Free, Allocated, From, R);  
  27.    {'EXIT', From, _ } ->  
  28.        check(Free, Allocated, From)  
  29.  end.  
  30. allocate([R|Free], Allocated, From) ->  
  31.    link(From),  
  32.    io:format("连接客户端进程~w~n",[From]),  
  33.    From ! {resource_alloc,{yes,R}},  
  34.    server(Free, [{R,From}|Allocated]);  
  35. allocate([], Allocated, From) ->  
  36.    From ! {resource_alloc,no},  
  37.    server([], Allocated).  
  38. free(Free, Allocated, From, R) ->  
  39.   case lists:member({R,From}, Allocated) of  
  40.    true ->  
  41.               From ! {resource_alloc,ok},  
  42.               Allocated1 = lists:delete({R, From}, Allocated),  
  43.               case lists:keysearch(From,2,Allocated1) of  
  44.                      false->  
  45.                             unlink(From),  
  46.                         io:format("从进程~w断开~n",[From]);  
  47.                      _->  
  48.                             true  
  49.               end,  
  50.              server([R|Free],Allocated1);  
  51.    false ->  
  52.            From ! {resource_alloc,error},  
  53.          server(Free, Allocated)  
  54.  end.  
  55.   
  56. check(Free, Allocated, From) ->  
  57.    case lists:keysearch(From, 2, Allocated) of  
  58.          false ->  
  59.            server(Free, Allocated);  
  60.         {value, {R, From}} ->  
  61.            check([R|Free],  
  62.            lists:delete({R, From}, Allocated), From)  
  63. end.  
  64. start_client()->  
  65.     Pid2=spawn(allocator,loop,[]),  
  66.     register(client, Pid2).  
  67. loop()->  
  68.     receive  
  69.         allocate->  
  70.             allocate(),  
  71.             loop();  
  72.         {free,Resource}->  
  73.             free(Resource),  
  74.             loop();  
  75.         stop->  
  76.             true;  
  77.         _->  
  78.             loop()  
  79.     end.  
  80.       

回家了,有空再详细说明下这个例子吧。执行:
java 代码
 
  1. 1> c(allocator).  
  2. {ok,allocator}  
  3. 2> allocator:start([1,2,3,4,5,6]).  
  4. true  
  5. 3> allocator:start_client().  
  6. true  
  7. 4> client!allocate  
  8. .  
  9. allocate连接客户端进程<0.37.0>  
  10.   
  11. 5> client!allocate.  
  12. allocate连接客户端进程<0.37.0>  
  13.   
  14. 6> client!allocate.  
  15. allocate连接客户端进程<0.37.0>  
  16.   
  17. 7> allocator:allocate().  
  18. 连接客户端进程<0.28.0>  
  19. {yes,4}  
  20. 8> client!{free,1}.  
  21. {free,1}  
  22. 9> client!{free,2}.  
  23. {free,2}  
  24. 10> client!allocate.  
  25. allocate连接客户端进程<0.37.0>  
  26.   
  27. 11> client!allocate.  
  28. allocate连接客户端进程<0.37.0>  
  29.   
  30. 12> client!stop.  
  31. stop  
  32. 13> allocator:allocate().  
  33. 连接客户端进程<0.28.0>  
  34. {yes,3}  
  35. 14> allocator:allocate().  
  36. 连接客户端进程<0.28.0>  
  37. {yes,2}  
  38. 15> allocator:allocate().  
  39. 连接客户端进程<0.28.0>  
  40. {yes,1}  
  41. 16>  




分享到:
评论

相关推荐

    Erlang编程规则——中文翻译版本

    从Erlang.org的Programming rules翻译的中文版本

    Erlang入门

    一本简约的Erlang语言入门书, 本书涵盖了从Erlang的起源到广泛应用的高可靠消息专递系统。

    Erlang入门:构建application练习2

    Erlang入门:构建application练习3,实例演示如果构建一个最简单的Erlang Application

    Erlang入门手册

    Erlang不但是一种编程语言,而且它具有比编程语言更加贴近操作系统的一些特性:并发线程、作业调度、内存管理、分布式、网络化等。据说使用Erlang编写的Yaws Web服务器,其并发性能是apache的15倍!

    Erlang入门:构建application练习5(监督树)

    Erlang入门:构建application练习5(监督树),以实例完全演示监督树的用法,Erlang入门必须知道的那点事

    erlang开发入门教程

    erlang是爱立信开发的程序开发语言,融合了函数式编程与面向对象编程,并行处理内建与程序语言内部,特别适合创建并发行、容错性、分布性要求比较高的软实时系统,掌握它程序员必备的一种编程技能,与它相似的语言...

    erlang入门级练习:LeetCode OJ问题的部分erlang 源码

    我自己在新学erlang,在LeetCode OJ上找了题目练习,题目很适合新手熟悉语言,但是LeetCode OJ里面只有几门主流语言的答案,下面是已完成的erlang源代码,后续有空再做其他问题续传,题目包含:(源码开头都有题目...

    erlang程序设计与入门

    erlang程序设计 erlang入门手册

    Erlang入门:构建application练习4(进程link的作用)

    Erlang入门:构建application练习4(进程link的作用),实例演示进程link的作用及效果

    icerlpcap:Erlang PCAP NIF——仅用于测试目的

    冰帽原作者:徐明勇( ) 这是 erlang PCAP NIF... 目前在 win7-8 和 WinPcap 4.1.2 上支持 erlang/x64。 警告:NIF 无效,并且会阻止 erlang VM。 为了获得更好的性能,请使用 epcap -- erlang 端口接口用于 PCAP: :

    erlang四大behaviour之四-supervisor

    erlang四大behaviour之四-supervisor

    Erlang入门ppt

    Erlang的入门介绍ppt,英文的,但是比较有味道.

    erlang编程 Introducing Erlang

    erlang入门电子书 erlang编程 Introducing Erlang,作者Simon.St.Laurent

    erlang入门学习经典资料(很不错)

    很不错的erlang学习资料。推荐刚上手的同学下载学习。

    erlang——Mnesia用户手册.pdf

    事件处理 5.8.调试.Mnesia.应用 5.9.Mnesia.里的并发进程 5.10.原型 5.11.Mnesia.基于对象的编程 6.Mnesia.系统信息 6.1.数据库配置数据 6.2.内核转储(Core.Dumps) 6.3.转储表 6.4.检查点 6.5....

    erlang编程指南

    erlang编程语言圣经 erlang从业人员的必备书籍 erlang语言开发入门

    erlang入门手册

    这个的确是个erlang入门的很好的教材,它通俗易懂,语言新意。

    erlang文献及资料汇总

    erlang异常处理详解 开发经验: 面对软件错误构建可靠的分布式系统 编写分布式的 Erlang 程序:陷阱和对策 硝烟中的Erlang 深入底层: erlang VM基于多核处理器的可伸缩性特征 erlang VM内部数据共享机制 erlang ...

    Erlang初级入门(英文pdf)

    该文档简要介绍了一种通用的面向并发的编程语言,它由瑞典电信设备制造商爱立信所辖的CS-Lab开发,目的是创造一种可以应对大规模并发活动的编程语言和运行环境。

    Erlang趣学指南

    (494页带目录的高清扫描版) 这是一本讲解Erlang编程语言的入门指南,内容通俗...内容涉及模块、函数、类型、递归、错误和异常、常用数据结构、并行编程、多处理、OTP、事件处理,以及所有Erlang的重要特性和强大功能。

Global site tag (gtag.js) - Google Analytics