Tickers in Lisp

Tickers are for when you want to do something repeatedly at regular intervals. Here’s an example of a ticker that ticks periodically until we stop it.

(defpackage :tickers
  (:use :cl :bordeaux-threads :local-time))

(in-package :tickers)

(defun main ()
  ;; Tickers use a similar mechanism to timers: a
  ;; channel that is sent values. Here we'll use a 
  ;; condition variable to await the values as they 
  ;; arrive every 500ms.
  (let ((ticker-cv (make-condition-variable))
        (ticker-lock (make-lock))
        (done nil))
    
    (make-thread
     (lambda ()
       (loop
         (with-lock-held (ticker-lock)
           (condition-wait ticker-cv ticker-lock :timeout 0.5)
           (if done
               (return)
               (format t "Tick at ~A~%" (now)))))))
    
    ;; Tickers can be stopped. Once a ticker
    ;; is stopped it won't receive any more values.
    ;; We'll stop ours after 1600ms.
    (sleep 1.6)
    (with-lock-held (ticker-lock)
      (setf done t)
      (condition-notify ticker-cv))
    
    (format t "Ticker stopped~%")))

(main)

When we run this program the ticker should tick 3 times before we stop it.

$ sbcl --load tickers.lisp
Tick at 2023-06-07T15:30:00.000000+01:00
Tick at 2023-06-07T15:30:00.500000+01:00
Tick at 2023-06-07T15:30:01.000000+01:00
Ticker stopped

In this Lisp version, we’re using the bordeaux-threads library for multi-threading and the local-time library for timestamp handling. The concept of channels is replaced with condition variables and locks, which are common synchronization primitives in Lisp.

The make-thread function creates a new thread that runs the ticker. The ticker uses condition-wait with a timeout to simulate the periodic ticking. The main thread sleeps for 1.6 seconds before signaling the ticker to stop.

Note that the exact timing and number of ticks may vary slightly due to differences in how threading and scheduling work in different Lisp implementations and operating systems.