zoukankan      html  css  js  c++  java
  • Erlang入门(四)——错误处理和鲁棒性

    去了趟福州,事情没搞定,托给同学帮忙处理了,回家休息了两天就来上班了。回家这几天最大的收获是第四次重读《深入Java虚拟机》,以前不大明了的章节豁然开朗,有种开窍的感觉,水到渠成,看来技术的学习还是急不来。
        闲话不提,继续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
    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


    四、运行时失败
        一个运行时错误将导致进程的非正常终止,伴随着非正常终止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}给连接进程

    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>



  • 相关阅读:
    设置debian6源
    debian7编译安装tengine添加lua和ldap模块
    elasticsearch5使用snapshot接口备份索引
    logstash5生成init脚本后台启动
    Xpack集成LDAP
    debian安装filebeat5.5收集nginx日志
    kibana5画图
    安装Xtrabackup,设置定时备份msyql数据库
    编译安装nrpe,配置监控mysql端口和主从状态
    编译安装keepalived,实现双主mysql高可用
  • 原文地址:https://www.cnblogs.com/xuan52rock/p/4597938.html
Copyright © 2011-2022 走看看