Next: Sessions/Debugging, Previous: Semaphores, Up: Threading

### 12.5 Waitqueue/condition variables

These are based on the POSIX condition variable design, hence the annoyingly CL-conflicting name. For use when you want to check a condition and sleep until it's true. For example: you have a shared queue, a writer process checking “queue is empty” and one or more readers that need to know when “queue is not empty”. It sounds simple, but is astonishingly easy to deadlock if another process runs when you weren't expecting it to.

There are three components:

- the condition itself (not represented in code)
- the condition variable (a.k.a waitqueue) which proxies for it
- a lock to hold while testing the condition

Important stuff to be aware of:

- when calling condition-wait, you must hold the mutex. condition-wait will drop the mutex while it waits, and obtain it again before returning for whatever reason;
- likewise, you must be holding the mutex around calls to condition-notify;
- a process may return from condition-wait in several circumstances: it is not guaranteed that the underlying condition has become true. You must check that the resource is ready for whatever you want to do to it.

         (defvar *buffer-queue* (make-waitqueue))
         (defvar *buffer-lock* (make-mutex :name "buffer lock"))

         (defvar *buffer* (list nil))

         (defun reader ()
           (with-mutex (*buffer-lock*)
             (loop
              (condition-wait *buffer-queue* *buffer-lock*)
              (loop
               (unless *buffer* (return))
               (let ((head (car *buffer*)))
                 (setf *buffer* (cdr *buffer*))
                 (format t "reader ~A woke, read ~A~%"
                         *current-thread* head))))))

         (defun writer ()
           (loop
            (sleep (random 5))
            (with-mutex (*buffer-lock*)
              (let ((el (intern
                         (string (code-char
                                  (+ (char-code #\A) (random 26)))))))
                (setf *buffer* (cons el *buffer*)))
              (condition-notify *buffer-queue*))))

         (make-thread #'writer)
         (make-thread #'reader)
         (make-thread #'reader)

— Structure: sb-thread:waitqueue

> Class precedence list: `waitqueue, structure-object, t`

> Waitqueue type.

— Function: sb-thread:make-waitqueue &key name

> Create a waitqueue.

— Function: sb-thread:waitqueue-name instance

> Name of a `queue`. Can be assingned to using `setf`. Queue names can be arbitrary printable objects, and need not be unique.

— Function: sb-thread:condition-wait queue mutex

> Atomically release `mutex` and enqueue ourselves on `queue`. Another thread may subsequently notify us using `condition-notify`, at which time we reacquire `mutex` and return to the caller.

— Function: sb-thread:condition-notify queue &optional n

> Notify `n` threads waiting on `queue`. The same mutex that is used in the corresponding `condition-wait` must be held by this thread during this call.

— Function: sb-thread:condition-broadcast queue

> Notify all threads waiting on `queue`.