附录D 可设为infinity的时钟

Guile的过程alarm提供了一种可中断的定时器机制。用户可以给这个时钟设置或重置一些时间片,或者让它停止。当时钟的定时器递减到0后,它就会执行用户之前设定的动作。Guile的alarm不是一个类似于第十五章第一节里定义的那种时钟,但是我们可以很容易的把它改造成那样。

时钟的定时器的初始状态是停止的,也就是说它不会随着时间流逝而被“触发”。如果想把定时器的触发时间设置为n秒(n不为0),运行(alarm n)。如果定时器已经被设定过了,那么(alarm n)就返回该定时器在本次设定前剩余的秒数。如果之前没有设定过,则返回0。

执行(alarm 0)让时钟的定时器停止,即定时器中的计数器【你可以理解为一个变量】不会随时间而递减,而且不会触发。(alarm 0)同样返回定时器在本次设定前剩余的秒数(如果之前设定过的话)。

默认情况下,当时钟的定时器计数减到0时,Guile会在控制台上显示一条消息并退出。更多的行为可以用过程sigaction来设定,如下所示:

(sigaction SIGALRM
  (lambda (sig)
    (display "Signal ")
    (display sig)
    (display " raised.  Continuing...")
    (newline)))

第一个参数SIGALRM(恰好是14)告诉sigaction需要设定的时钟处理函数[1]。第二个参数是一个用户指定的单参数过程。在这个例子里,当时钟触发时,处理函数会在控制台上显示"Signal 14 raised. Continuing..."而不是退出Scheme(14是变量SIGALRM的值,时钟会把它传递给它对应的处理过程,我们现在先不考虑这个)。

从我们的角度看,这种简单的定时器机制有一个问题。过程alarm的返回值0的意义是不明确的:既可能是指时钟处于停止状态,也有可能是刚好计时器减到了0。我们可以通过在时钟的算法里引入*infinity*来解决这个问题。换句话说,我们需要的时钟与alarm基本上是差不多的,除了一点,那就是如果时钟停止的话,那么它有*infinity*秒。这样就看起来比较自然了。

  1. (clock n)对于一个停止的时钟返回*infinity*,而不是0。
  2. 如果让时钟停止,执行(clock *infinity*),而不是(clock 0)
  3. (clock 0)相当于给时钟设置一个无限小的时间,也就是让它立即触发。

在Guile中,我们可以把*infinity*定义为如下的“数”:

(define *infinity* (/ 1 0))

我们用alarm来定义clock

(define clock
  (let ((stopped? #t)
        (clock-interrupt-handler
         (lambda () (error "Clock interrupt!"))))
    (let ((generate-clock-interrupt
           (lambda ()
             (set! stopped? #t)
             (clock-interrupt-handler))))
      (sigaction SIGALRM
                 (lambda (sig) (generate-clock-interrupt)))
      (lambda (msg val)
        (case msg
          ((set-handler)
           (set! clock-interrupt-handler val))
          ((set)
           (cond ((= val *infinity*)
                  ;This is equivalent to stopping the clock.
                  ;This is almost equivalent to (alarm 0), except
                  ;that if the clock is already stopped,
                  ;return *infinity*.

                  (let ((time-remaining (alarm 0)))
                    (if stopped? *infinity*
                        (begin (set! stopped? #t)
                          time-remaining))))

                 ((= val 0)
                  ;This is equivalent to setting the alarm to
                  ;go off immediately.  This is almost equivalent
                  ;to (alarm 0), except you force the alarm
                  ;handler to run.

                  (let ((time-remaining (alarm 0)))
                    (if stopped?
                        (begin (generate-clock-interrupt)
                          *infinity*)
                        (begin (generate-clock-interrupt)
                          time-remaining))))

                 (else
                  ;This is equivalent to (alarm n) for n != 0.
                  ;Just remember to return *infinity* if the
                  ;clock was previously quiescent.

                  (let ((time-remaining (alarm val)))
                    (if stopped?
                        (begin (set! stopped? #f) *infinity*)
                        time-remaining))))))))))

过程clock用到了三个内部状态变量:

  1. stopped?,表示时钟是否是停止的;
  2. clock-interrupt-handler,一个过程,表示用户希望在时钟触发后执行的动作;
  3. generate-clock-interrupt,另一个过程,该过程会在运行用户定义的时钟处理过程前把stopped?设为false

过程clock有两个参数。如果第一个参数是set-handler,那么就把第二个参数作为时钟处理器。

如果第一个参数是set,就把该时钟触发时间设置为第二个参数,返回本次设定前定时器剩余的秒数。代码对0*infinity*以及其他时间值的处理是不同的,这样用户可以得到一个算术上对alarm透明的接口。


[1]. 还有一些其他的信号和与之相应的处理器,sigaction同样可以使用它们。