Clojure宏练习: 让代码运行指定的时间段

在模拟某些事件的时候, 我们可能需要一个无限循环, 例如哲学家进餐的例子中, 就可能会用一个无限循环来模拟一个哲学家进餐的过程. 这样的弊端是, 一旦运行, 我们的REPL就无法返回, 唯一的办法就是强行终止JVM, 然后重开. 而实际上, 我们不一定需要这样做, 将无限循环变成运行指定的时间段同样可以达到模拟的效果, 而系统可以更加优雅的结束.

和dotimes一样, 我们可以用macro来做到这一点, 区别只是一个指定次数, 一个指定时间.

 
(defmacro run-for [milliseconds loop-action]
  `(let [start-time# (System/currentTimeMillis)]
    (loop [current-time# start-time#]
      (if (> (- current-time# start-time#) ~milliseconds) nil (do ~loop-action (recur (System/currentTimeMillis)))
      )
    )
  )
)
 

这里时间单位是毫秒, 需要循环的action即loop-action, 将会运行在一个loop当中, 每次循环结束之后判断流逝的时间, 如果超过了第一个参数指定的毫秒数, 循环结束.

先来看看1000毫秒内可以执行多少次println

 
(run-for 1000 (do  (println "hello in 1000 millis")))
 

结果是3816次. 如果将每次action的执行时间设置为200毫秒, 那么结果应该是执行5次.

 
(run-for 1000 (do (Thread/sleep 200) (println "hello in 1000 millis")))
 

假设有下面的函数

 
(defn foo []
  (let [a 3]
    (while true
      (println "do something in forever loop: " a)
    )
  )
)
 

要将无限循环替换为在指定的时间段内运行, 方法如下

 
(defn foo []
  (let [a 3]
    (run-for 1000
    ;(while true
      (println "do something in forever loop: " a)
    ;)
    )
  )
)