首页 > Erlang并发教程 > 6.5 Erlang并发编程-进程超时
2013
11-14

6.5 Erlang并发编程-进程超时

超时

Erlang中用于接收消息的基本原语receive可以通过添加一个可选的超时子句来进行增强,完整的语法变成这样:

receive
    Message1 [when Guard1] ->
        Actions1 ;
    Message2 [when Guard2] ->
        Actions2 ;
    ...
after
    TimeOutExpr ->
        ActionsT
end

TimeOutExpr是一个整数值表达式,表示毫秒数。时间的精确程度受到具体Erlang实现的底层操作系统以及硬件的限制——这是一个局部性问题(local issue)。如果在指定的时间内没有任何消息被匹配到,超时将会发生,ActionsT会被执行,而具体什么时候执行则是依赖与很多因素的,比如,和系统当前的负载有关系。

例如,对于一个窗口系统,类似于下面的代码可能会出现在处理事件的进程中:

get_event() ->
    receive
        {mouse, click} ->
            receive
                {mouse, click} ->
                    double_click
            after double_click_interval() ->
                single_click
            end
        ...
    end.

在这个模型中,事件由消息来表示。get_event函数会等待一个消息,然后返回一个表示对应事件的原子式。我们希望能检测鼠标双击,亦即在某一个较短时间段内的连续两次鼠标点击。当接收到一个鼠标点击事件时我们再通过receive试图接收下一个鼠标点击事件。不过,我们为这个receive添加了一个超时,如果在指定的时间内(由double_click_interval指定)没有发生下一次鼠标点击事件,receive就会超时,此时get_event会返回single_click。如果第二个鼠标点击事件在给定的超时时限之内被接收到了,那么get_event将会返回double_click

在超时表达式的参数中有两个值有特殊意义:

infinity

原子式infinity表示超时永远也不会发生。如果超时时间需要在运行时计算的话,这个功能就很有用。我们可能会希望通过对一个表达式进行求值来得到超时长度:如果返回值是infinity的话,则永久等待。

0

数值0表示超时会立即发生,不过在那之前系统仍然会首先尝试对邮箱中已有的消息进行匹配。

receive中使用超时比一下子想象到的要有用得多。函数sleep(Time)将当前进程挂起Time毫秒:

sleep(Time) ->
    receive
        after Time ->
            true
    end.

flush_buffer()清空当前进程的邮箱:

flush_buffer() ->
    receive
        AnyMessage ->
            flush_buffer()
        after 0 ->
            true
    end.

只要邮箱中还有消息,第一个消息会被匹配到(未绑定变量AnyMessage会匹配到任何消息,在这里就是第一个消息),然后flush_buffer会再次被调用,但是如果邮箱已经为空了,那么函数会从超时子句中返回。

消息的优先级也可以通过使用0作为超时长度来实现:

priority_receive() ->
    receive
        interrupt ->
            interrupt
    after 0 ->
        receive
            AnyMessage ->
                AnyMessage
        end
    end

函数priority_receive会返回邮箱中第一个消息,除非有消息interrupt发送到了邮箱中,此时将返回interrupt。通过首先使用超时时长0来调用receive去匹配interrupt,我们可以检查邮箱中是否已经有了这个消息。如果是,我们就返回它,否则,我们再通过模式AnyMessage去调用receive,这将选中邮箱中的第一条消息。

程序 5.4

-module(timer).
-export([timeout/2,cancel/1,timer/3]).

timeout(Time, Alarm) ->
    spawn(timer, timer, [self(),Time,Alarm]).

cancel(Timer) ->
    Timer ! {self(),cancel}.

timer(Pid, Time, Alarm) ->
    receive
        {Pid,cancel} ->
            true
    after Time ->
        Pid ! Alarm
    end.

receive中的超时纯粹是在receive语句内部的,不过,要创建一个全局的超时机制也很容易。在程序5.4中的timer模块中的timer::timeout(Time,Alarm)函数就实现了这个功能。

调用timer:timeout(Time, Alarm)会导致消息Alarm在时间Time之后被发送到调用进程。该函数返回计时器进程的标识符。当进程完成自己的任务之后,可以使用该计时器进程标识符来等待这个消息。通过调用timer::cancel(Timer),进程也可以使用这个标识符来撤销计时器。需要注意的是,调用timer:cancel并不能保证调用进程不会收到Alarm消息,这是由于cancel消息有可能在Alarm消息被发送出去之后才被收到的。


6.5 Erlang并发编程-进程超时》有 1 条评论

留下一个回复

你的email不会被公开。