附录D 可设为infinity的时钟

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

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

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

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

  1. (sigaction SIGALRM
  2. (lambda (sig)
  3. (display "Signal ")
  4. (display sig)
  5. (display " raised. Continuing...")
  6. (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*定义为如下的“数”:

  1. (define *infinity* (/ 1 0))

我们用alarm来定义clock

  1. (define clock
  2. (let ((stopped? #t)
  3. (clock-interrupt-handler
  4. (lambda () (error "Clock interrupt!"))))
  5. (let ((generate-clock-interrupt
  6. (lambda ()
  7. (set! stopped? #t)
  8. (clock-interrupt-handler))))
  9. (sigaction SIGALRM
  10. (lambda (sig) (generate-clock-interrupt)))
  11. (lambda (msg val)
  12. (case msg
  13. ((set-handler)
  14. (set! clock-interrupt-handler val))
  15. ((set)
  16. (cond ((= val *infinity*)
  17. ;This is equivalent to stopping the clock.
  18. ;This is almost equivalent to (alarm 0), except
  19. ;that if the clock is already stopped,
  20. ;return *infinity*.
  21. (let ((time-remaining (alarm 0)))
  22. (if stopped? *infinity*
  23. (begin (set! stopped? #t)
  24. time-remaining))))
  25. ((= val 0)
  26. ;This is equivalent to setting the alarm to
  27. ;go off immediately. This is almost equivalent
  28. ;to (alarm 0), except you force the alarm
  29. ;handler to run.
  30. (let ((time-remaining (alarm 0)))
  31. (if stopped?
  32. (begin (generate-clock-interrupt)
  33. *infinity*)
  34. (begin (generate-clock-interrupt)
  35. time-remaining))))
  36. (else
  37. ;This is equivalent to (alarm n) for n != 0.
  38. ;Just remember to return *infinity* if the
  39. ;clock was previously quiescent.
  40. (let ((time-remaining (alarm val)))
  41. (if stopped?
  42. (begin (set! stopped? #f) *infinity*)
  43. time-remaining))))))))))

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

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

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

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


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