进程错误处理机制

在处理系统的容错性和高可用性方面,Erlang有着它自己的独到之处,其根本原因是Erlang系统拥有构建于语言并发模型中的简单且功能强大的各种构造。这些构造允许进程相互监控并从软件故障中恢复。它们使Erlang相对于其它编程语言更具有竞争优势,因为通过隔离错误,确保不间断运行而提供的容错性,方便了复杂架构的开发。

进程链接和退出信号

1.内置函数link/1需要一个进程标识符作为参数,它会在调用进程和进程标识符所表示的进程之间建立一个双向联系,内置函数spawn_link/3会创建紧跟着调用spawn/3和link/1的结果是相同的,除了它是基元的(即调用在一个步骤里发生,因此调用要么都成功要么都失败)。

2.因为联系是双向的,所以进程A与B相连或者进程B与A相连是无关紧要的,其结果都是相同的。如果一个链接进程异常终止,退出信号将会发送到与这个终止进程相连的所有进程。接受这一信号的进程将退出,并会把一个新的退出信号传递给与现在这个进程相连的所有进程(这个集合也成为链接集)。

3.退出信号是具有{'EXIT',Pid,Reason}格式的元组,它包括基元'EXIT',终止进程的进程标识符Pid和终止的原因Reason,接收的进程将以同样的原因终止,并产生一个新的带有它自己的进程标识符的退出信号,然后向它自己的链接集传送。

4.当进程A失败的时候,它的退出信号传递到进程B,进程B将以与A同样的理由终止,然后它的退出信号又会传递到进程C,如果在一个系统中有一个相互依赖的进程组,那么把它们链接起来是一个很好的设计实践,用以确保如果一个进程终止的时候,它们都会终止。

捕捉退出信号

1.进程可以通过设定trap_exit,然后执行函数调用process_flag(trap_exit,true)来捕获退出信号,通常在初始化函数中实现这个调用,它允许退出信号转换为{'EXIT',Pid,Reason}的格式。如果进程捕获退出信号,这些消息会和其它消息一样保存在信箱中。可以使用receive结构重新找回这些消息,也可以像对待其它消息一样对它们进行模式匹配。

2.如果捕捉到了退出信号,它就不会在进一步传送。除了已经终止的进程,它链接集中的所有进程都不会受到影响,捕捉退出信号的进程通常标记为双圆。

3.当一个进程生成的时候将trap_exit标签默认设置为false。

监控内置函数

1.链接是双向的。为了单向监控进程,可以把内置函数erlang:monitor/2添加到Erlang中,可以如下调用:

erlang:monitor(process,Proc)。

这样就生成了一个调用进程到另一个标记为Proc进程的监控进程,Proc可以是进程标识符也可以是注册的名字,当有进程标识符的进程终止的时候,消息{'DOWN',Reference,process,Pid,Reason}会发送到监控进程,这个消息包含一个对监控进程的引用。它是一个唯一的值,可以使用它来识别实体,例如对特定请求的响应,也可以比较引用的相等性,把它应用在模式匹配的定义中。

2.如果尝试链接到不存在的进程,这个调用进程会因为运行时错误而终止,内置函数monitor的行为有所不同,如果调用monitor的时候Pid已经终止(或根本不存在),则会立即发送带有Reason是noproc的'DOWN'消息。重复调用erlang:monitor(process,Pid)会返回不同的引用,从而建立多个独立监控,当‘Pid’终止的时候,它们都将发出自己的'DOWN'消息。

3.监控进程可以通过调用erlang:demonitor(Reference)删除,在调用demonitor之前'DOWN'信息可能已经发送,因此使用监控进程时不应该忘记刷新它的信箱。为了保险起见,可以在erlang:demonitor(Reference,[flush]),它在关闭监控的同时会删除由Reference提供的所有'DOWN'信息。

4.选择监控还是链接:建立链接是永久的,如在监控树形结构中,或者当希望退出信号的传送是双向的时候,监控器用来检测客户端调用的行为进程是比较理想的,如链接到另一个进程而造成客户端终端,你不希望影响你所调用的进程状态,也不希望使其接受一个退出信号的时候。

内置函数exit

1.内置函数调用exit(Reason)来使调用进程终止,并把终止原因Reason作为参数传递给内置函数。终止进程会产生退出信号并发送到所有和它相连的进程,如果exit在try...catch构造中调用,则可以在catch中捕获它。

2.如果想发送一个退出信号到一个特定的进程,可以通过使用exit(PidB,Reason)来调用内置函数exit,它的结果和进程发送终止信息到其链接集几乎相同,除了{'EXIT',Pid,Reason}中的Pid是发送进程本身,而不是终止进程(例如,进程A和B是链接的,如果此时B终止了,那么发送给A的退出信号中的Pid是B的Pid;如果A对B做了退出命令,那么A发送给B的退出信号中的Pid是A的Pid),如果接收进程捕获到退出信号,信号会转换为退出消息并保存到进程的信箱中,如果进程不捕获退出消息且原因不是normal,那么它将终止并产生和传递一个退出信号。发送给一个进程的退出信号不能通过catch捕获,它会导致该进程终止。

内置函数和术语

1.术语

链接是在进程之间为退出信号建立的进行双向传送的路径。

退出信号是一个进程传输的信号,它包括进行进程结束的终止原因。

错误捕捉表明一个进程处理退出信号的能力,就仿佛它们只是一般消息一样。

2.与并发错误处理相关的内置函数包括:

link(Pid):在调用进程和Pid之间设置一个双向链接。

unlink(Pid):移除一个到Pid的链接。

spawn_link(Mod,Fun,Args):原子性的生成进程并设置一个调用进程和新生成进程之间的链接。

spawn_monitor(Mod,Fun,Args):原子性的生成进程并设置一个调用进程和新生成进程之间的监控。

process_flag(trap,exit):把当前进程的退出信号转换为退出消息,Flag可以是基元true(开启捕捉退出信号)或者基元false(关闭捕捉退出信号)。

erlang:monitor(process,Pid):生成一个针对Pid的单向监控,它返回给调用进程一个用来识别模式匹配的终止进程的引用。

erlang:demonitor(Reference):清除监控从而使监视不再进行,不要忘记刷新信息,它可能已经在调用内置函数demonitor之前到达了。

erlang:demonitor(Reference,[flush]):和demonitor/1一样,但如果它是作为一个竞争条件的结果发送的时候回删除{_,Reference,_,_,_}消息。

exit(Reason):导致调用进程以Reason终止。

exit(Pid,Reason):发送一个退出信号到Pid,但是实际上发送到退出进程的信号的Pid是调用进程的Pid。

3.Pid=spawn_link(Module,Function,Args)和link(Pid=spawn(Module,Function,Args))的区别

如果使用spawn_link/3,那么将生成进程并链接到父进程,这个操作是无法在spawn和link之间停止,因为所有内置函数都是基元不可分的,最早可能终止进程的时间是在执行内置函数之后。

如果取而代之把spawn和link作为两个单独的操作来运行,父进程在调用spawn生成进程并且绑定变量Pid之后,但在调用link之前,可能被暂停,新进程开始执行,遇到运行错误并终止,父进程于是抢先执行,然后第一件事情是链接到不存在的进程,这将导致运行时错误,而不是接受到一个退出信号。

经验法则是始终使用spawn_link。除非需要在链接和断开之间切换,或者它不是一个链接到进程的子进程。

传送语义

1.当一个进程终止的时候,它会给它的链接集中的进程发送一个退出信号,这些信号可以是正常的或者是非正常的,当没有更多的代码可以执行而使进程结束,或通过调用带有normal原因的内置函数exit的时候,产生正常退出信号。

2.当收到一个非正常退出信号的时候,一个没有捕获退出信号的进程会终止,而忽略带有原因normal的退出信号。一个捕捉退出信号的进程会把所有进来的正常的和非正常的信号,转化为常规的消息存储到信箱中,它们会通过receive语句处理。

3.在任何的内置函数exit中,如果Reason是kill,不管trap_exit的值是什么,这个进程都将会无条件终止。一个带有kill原因的退出信号会传送到它的链接集,这会确保如果链接集合的任何一端无条件终止,那么也不会终止捕获退出信号的进程。

4.    Reason                           正常退出trap_exit=true                                 非正常退出trap_exit=false    

       normal                            收到{'EXIT',Pid,Reason}                                没有任何事情发生

       kill                                  当Reason为kill时,无条件终止                        当Reason为kill时,无条件终止

       other                              收到{'EXIT',Pid,Other}                                    当Reason为other时,无条件终止

健壮性系统

 1.在Erlang中,通过分层建立健壮性系统。通过使用进程,可以生成一棵树,而树的叶子由处理操作的应用层组成,树的内部节点监测它的叶子和在它们之下的其它节点,任何级别的进程都会捕捉比它们更低级别的进程的错误,如果进程的唯一任务是监控子进程,在这种情况下是指树的节点,称为监控进程。一个执行操作的叶进程称为工作进程。当提到子进程时,我们指的是监控进程和归属于这个监控进程的工作进程。

2.在设计良好的系统中,应用程序员不需要担心错误处理部分的代码,如果一个工作进程崩溃,退出信号发送到它的监控进程,监控进程会把它和系统的更高层次隔离开,基于预先设置的参数和终止的原因,监控进程决定这个工作进程是否重新启动。

3.然而监控进程可能不是唯一监控其它进程的进程。如果一个进程依赖于另一个进程,它不一定是后者的子进程,它可以把自身和后一个进程链接。当发生异常终止的时候,这两个进程可以采取适当的行动。

4.在大型Erlang系统中,永远都不应该允许有不属于监控树任何一部分的进程存在:所有的进程要么与监控进程相连,要么与其它工作进程相连。因为Erlang系统可以多年不启动而持续运行,即使不是数十亿,在系统运行生命周期内也可能生成数百万的进程。需要对这些进程完全控制,并在必要的时候能关闭整个监控树,永远不知道错误是通过什么形式表现出来的,我们想避免一个不正常的进程,由于它没有链接到监控进程,而最终导致我们无法对其进行终止。另外一个危险是处于暂停的进程,它可能由于错误或者超时,从而导致内存泄漏,这个可能需要数个月的时间去查明。

5.注意,想象一下在升级的时候,你不得不以特定类型的调用去终止所有进程。如果这些进程是监控树的一部分,那么需要做的是终止高一层的监控进程,然后升级代码并重新启动。一想到我们不得不进入终端,然后手动查找并终止所有没有和父进程或者监控进程链接的进程。如果你不知道有什么进程在你的系统中运行,那么唯一可行的方法是重新启动终端,而这是违背高可用性原则的。

6.如果很在乎系统的高可用性和容错性,那么请确保所有的进程都与监控树相连。

监控进程

1.监控进程的唯一作用是启动工作进程并监控,现实生活中的实现方式:工作进程要么在监控进程的初始化阶段启动,要么在监控进程启动之后动态启动。一个监控进程可以捕捉退出信号,并在生成工作进程的时候链接到它们,如果一个工作进程终止,那么监控进程会收到退出信号,然后监控进程可以使用工作进程在退出信号中的Pid来识别这个进程并重新启动它。

2.监控进程应当管理进程的终止,使用统一的方式来启动它们和决定采取何种行动,这些行动可能包括不做任何行动,重新启动该进程或者终止整个子树,让它的监控进程来解决这个问题。

3.不论系统如何,监控进程的行为应该一致。它与客户端/服务器、有限状态机,事件句柄一起,被认为是进程设计模式:

监控进程的通用行为是启动工作进程,监控它们,并在它们万一终止时重新启动。

监控进程的特定行为由工作进程组成,包括何时、如何启动和重新启动它们。

猜你喜欢

转载自yansxjl.iteye.com/blog/2358844