`

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编程规则的中文翻译版本中的部分内容进行详细解析,包括Erlang术语、SW工程原则、程序、服务器和消息处理、Erlang特殊约定、常见错误以及所需文档约定等。 Erlang术语部分提到了Erlang程序的模块化...

    Erlang入门

    Erlang的OTP(Open Telecom Platform)框架提供了许多用于构建分布式系统的组件和服务,如监控、日志、错误管理等。 **五、热代码升级** Erlang的一大亮点是其热代码升级功能。开发者可以在不中断系统运行的情况下...

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

    这意味着可以在不停机的情况下对系统进行升级或修复错误,极大地提高了系统的可用性和稳定性。 - **电信领域的应用**:由于Erlang的高性能和高可靠性特点,它在电信领域得到了广泛应用,尤其是在构建电话交换机等...

    erlang开发入门教程

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

    erlang 入门练习

    总结,`erlang 入门练习`这个主题涵盖了Erlang的基础语法、并发模型、错误处理、标准库的使用等内容。通过分析`client.erl`,我们可以逐步了解Erlang编程的核心概念,并进一步深入学习这个强大的并发编程语言。在...

    Erlang入门ppt

    同时,Erlang的错误处理机制和容错设计使得系统能够在出现错误时继续运行,这在电信这样要求高可用性的领域至关重要。 总的来说,Erlang是一种针对电信行业需求设计的编程语言,结合了函数式编程和并发性,具有强大...

    Erlang初级入门(英文pdf)

    - **鲁棒性**:具有良好的故障恢复机制,确保系统在出现错误时仍能保持稳定运行。 - **分布式计算**:支持分布式应用程序的开发,使不同节点之间的通信变得简单。 - **热代码加载**:可以在不重启系统的情况下更新...

    erlang书籍

    Erlang的并发模型和错误恢复机制使得它在构建高可靠性系统中独树一帜,如电话交换系统、分布式数据库、实时通信软件等。同时,Erlang的轻量级进程和消息传递机制使其在处理大规模并发时表现出色,非常适合现代云计算...

    erlang资源

    这个“erlang资源”包含两本PDF书籍——《Erlang并发编程》和《Erlang入门手册》,它们是深入理解和学习Erlang语言的关键资料。 《Erlang并发编程》这本书可能涵盖了以下知识点: 1. **并发模型**:Erlang的并发...

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

    Erlang是一种面向并发的、函数式编程语言,特别适合于构建高可用性和容错性的分布式系统。在Erlang中,"应用"(application)是组织代码的基本单元,它包含了模块、配置文件以及启动和停止应用程序的逻辑。在这个...

    Erlang入门:构建application练习2

    Erlang是一种面向并发的、函数式编程语言,由瑞典电信设备制造商Ericsson开发,用于构建高可用性、分布式和实时系统。在本教程中,我们将深入探讨如何使用Erlang构建一个名为"Application"的基本应用程序,这在...

    erlang程序设计与入门

    OTP是Erlang的开发框架,提供了标准库、设计原则和工具,包括行为(如GenServer、GenEvent)、应用管理和错误处理机制等,帮助开发者构建更加稳定和可维护的系统。 8. **Eshell和REPL** Erlang的交互式Shell...

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

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

    两本erlang电子书

    同时,它也会涉及Erlang的并发特性和错误处理机制,使读者能够编写出可扩展且容错性强的代码。此外,这本书还会讨论Erlang的模块系统、类型系统以及如何利用REPL(Read-Eval-Print Loop)进行调试和测试。 这两本书...

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

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

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

    它允许开发者构建出能够优雅处理错误和异常的分布式系统,这是Erlang在高可用性和容错性领域中的强大之处。然而,使用`link`也需要注意,不当的链接可能导致进程意外退出,因此在设计系统时应谨慎考虑进程间的链接...

    erlang入门手册

    - **错误处理**: Erlang采用了一种独特的错误处理方式,任何进程崩溃时,不会影响其他进程,这为系统的稳定性提供了保障。 - **增强健壮性的例子**: 文档通过具体的例子展示了如何使用Erlang的特性来编写更加健壮的...

    erlang——gen-server.pdf

    Erlang中的`gen_server`...它通过提供标准接口、错误处理和状态管理,简化了并发编程的复杂性,同时保持了Erlang系统的高度灵活性和容错性。理解并熟练运用gen_server,对于开发高效能、高可用性的Erlang应用至关重要。

Global site tag (gtag.js) - Google Analytics