The Common Lisp Cookbook - Threads

Contents

Introduction

By threads, I mean separate execution strands within a single Lisp process, sharing the same address space. Typically, execution is automatically switched between these strands by the system (either by the lisp kernel or by the operating system) so that tasks appear to be completed in parallel (asynchronously). This page discusses the creation and management of threads and some aspects of interactions between them. For information about the interaction between lisp and other processes, see Interfacing with your OS.

Unfortunately, an instant pitfall for the unwary is that most implementations refer (in nomenclature) to threads as processes - this is a historical feature of a language which has been around for much longer than the term thread. Call this maturity a sign of stable implementations, if you will.

The ANSI Common Lisp standard doesn't mention this topic. So almost everything that can be said here depends on your OS and your implementation. It's not just a question of different symbols in different packages (although that might be enough to get you started, so see the acl-compat package at Sourceforge and in particular the files whose names match "acl-mp-*.lisp") - concepts can vary too.

Speaking of implementations, the following discussion currently refers only to LispWorks. It has been tested on LispWorks for Windows Professional version 4.2.6 running on Windows NT (SP 6). All the examples below (apart from those few which require resaving the lisp image) should work without difficulty on the (free) Personal edition running under either Linux or Windows.

Your mileage may vary. I will add "support" for other implementations and operating systems to this page later, if I have the time and access to the appropriate materials.

In any case, read the manual! In this instance, the manual concerned is the chapter on "The MP Package" in the "LispWorks Reference Manual".

Pre-requisite: I assume familiarity with Common Lisp, in particular with closures, the loop macro, and lambda forms.

Why bother?

The first question to resolve is: why bother with threads? Sometimes your answer will simply be that your application is so straightforward that you need not concern yourself with threads at all. But in many other cases it's difficult to imagine how a sophisticated application can be written without multi-threading. For example:

Emergency exits

It's fairly likely when you're learning to work with threads that - every now and again - one of them will run away from you and either fall motionless at your feet or make some CPU-intensive break for freedom. If you've managed to get your lisp tangled you may be short on options for halting a runaway thread, short of killing the lisp altogether.

Before you go any further, you might want to locate the "Process Browser". Aptly enough, you'll find it on the LispWorks podium under the icon of somebody running away. Open one before you get into trouble. To kill a process which won't respond to polite coercion, select it in the Process Browser and then click the "skull and crossbones" icon.

Exercise: Type (mp:process-run-function "Foo" () (lambda () (loop))) into a listener and use the Process Browser to kill the thread.

Basics

In order to run a function in a separate thread, you need to do two things.

  1. Make sure that multithreading is running. By default, multithreading is always running in the LispWorks for Windows environment so in this case there is nothing to do. See initializing multithreading below to find out how to get threads running on other platforms.
  2. Now call your function, in its new thread. For example:
    (defvar *foo* 0)
    (defun foo () (incf *foo*))
    (mp:process-run-function "Incrementing *foo*" nil 'foo)
    *foo*  => 1
    

In the above example, you created a new thread called "Incrementing *foo*". The function foo was invoked (with no arguments) in that thread. When foo returned, the thread no longer had any work to do and so was terminated.

Note the following:

Another simple example:

CL-USER 15 > (mp:process-run-function "sleep in the background" nil 'sleep 10)
#<MP:PROCESS Name "sleep in the background" Priority 850000 State "Running">

CL-USER 16 > (mp:find-process-from-name "sleep in the background")
#<MP:PROCESS Name "sleep in the background" Priority 0 State "Sleeping on mailbox">

CL-USER 17 > (sleep 10)  ;; At this point the listener sleeps for ten seconds -
NIL                      ;; long enough for the new thread to run to completion.

CL-USER 18 > (mp:find-process-from-name "sleep in the background")
NIL                      ;; The sleeping thread has finished its job and terminated.

CL-USER 19 >

Closures! Warning! Note the difference between:

(dotimes (i 20)
  (mp:process-run-function "One closure" ()
                           (lambda () (print i #.*standard-output*))))

and

(dotimes (i 20)
  (mp:process-run-function "Twenty different bindings" ()
                           (lambda (j) (print j #.*standard-output*))
                           i))

In the first case, all twenty threads share the same closure variable i. The threads execute asynchronously - in other words there's no way to tell exactly when or in what order they'll execute, or how they'll interleave with the thread which created them. In this case (try it!) you will observe that LispWorks initialises all 20 threads before any of them have a chance to start running. By far the easiest way of dealing with this is to ensure that variables which need to be private to each thread are bound on a per-thread basis.

Exercise: get two or three new threads running simultaneously, and convince yourself they're all there.

Where's my output?

An obvious way to test whether threads are behaving as you imagine they ought, is to get them to print messages to the listener. For example, you might feel justified in trying something like:

CL-USER 23 > (mp:process-run-function "test" () (lambda () (print 99)))
#<MP:PROCESS Name "test" Priority 850000 State "Running">

CL-USER 24 > ;; Where's my output?

Where indeed is your output? The answer is that your new thread has a different *standard-output* to the listener, and that's where your output has gone to. Here is how you might find out where precisely that is:

CL-USER 25 > (mp:process-run-function
              "" ()
              (lambda ()
                (print *standard-output* #.*standard-output*)))
#<MP:PROCESS Name "" Priority 850000 State "Running">

#<Synonym stream to *TERMINAL-IO*>
CL-USER 26 >

Exercise: Open the console (i.e. *terminal-io*) by evaluating (read-char *terminal-io*); type a #\Newline into the console to return from read-char. Now you can prove directly where a thread's output goes (by sending something there). Warning! Don't be tempted to close the console window, or you'll lose your lisp.

Waiting

In all the above examples, a thread is created to run a simple function and then halt. In a typical application at least some of your threads will run an event loop of some sort. An event loop is a function which repeatedly waits for an external event to occur. When an event is noticed, it is dispatched (maybe to another thread) for processing and the event loop cycles back to its waiting state. The "other thread" here might already exist (perhaps running an event loop of its own) or might be created specifically to perform this task (i.e. handle a single event) and then terminate.

It might be tempting to construct an event loop using cl:sleep. For example:

(defun bogus-event-loop ()
  (loop
   (sleep 1)                        ; THIS IS WRONG
   (when (something-has-happened)
     (act-on-that-thing))))

(mp:process-run-function "Bogus event loop" ()
                         'bogus-event-loop)

This is a poor choice, because you're condemned to waiting for the sleep to return before you can perform the wake-up test. (Also, it's possible that your implementation cannot sleep one single thread without sleeping the whole lisp process. In such cases, any processing required before the predicate something-has-happened can return true will never happen.)

Consider instead:

(defun improved-event-loop ()
  (loop
   (mp:process-wait "Waiting for something to happen"
                    'something-has-happened)
   (act-on-that-thing)))

The arguments to mp:process-wait are a string (which you should use for describing what this thread is waiting for), a function and optionally arguments to that function. The current thread will effectively sleep until the function returns true. The function can watch either internal state...

(defun flush-entries-to-file (entries-symbol max-length file)
  (loop
   ;; Wait until we have enough entries to justify going to disk.
   (mp:process-wait (format nil "Waiting for ~a entr~:@p" max-length)
                    (lambda ()
                      (>= (length (symbol-value entries-symbol)) max-length)))
   ;; In this example, don't bother to spawn off a new thread to
   ;; perform the task.
   (let ((entries (shiftf (symbol-value entries-symbol) nil)))
     (with-open-file (ostream file
                              :direction :output
                              :if-exists :append
			      :if-does-not-exist :create)
       (format ostream "~%Flushing entries:")
       (dolist (entry (reverse entries))
         (print entry ostream))))))

;; Test bed to drive the event loop
(defvar *test-entries* nil)
(defvar *test-file* "c:/temp/test-flush-entries.txt")
(defun test-flush-entries-to-file ()
  (let ((tester
         (mp:process-run-function "Test writing entries to file" ()
                                  'flush-entries-to-file
                                  '*test-entries*
                                  10
                                  *test-file*)))
    (dotimes (i 100)
      (push i *test-entries*)
      ;; Without the delay introduced by sleep, all 100 entries are
      ;; generated before the flusher has a chance to wake up.
      (sleep 0.1))
    (mp:process-kill tester)))

... or external state...

(defun flush-entries-from-file (file reporting-stream)
  (loop
   ;; Wait for given file to exist on disk.
   (mp:process-wait (format nil "Waiting for ~a" file)
                    'probe-file file)
   ;; Empty the file to the reporting-stream, being careful to
   ;; allow more contents to accumulate while this is happening.
   (format reporting-stream "~&Reading ~a:~%" file)
   (let ((temp-file (format nil "~a.temp" file)))
     (rename-file file temp-file)
     (with-open-file (istream temp-file)
       (loop (let ((line (read-line istream nil)))
               (unless line
                 (return))
               (write-line line reporting-stream))))
     (delete-file temp-file))))

(defun test-flush-entries-from-file ()
  (delete-file *test-file*)
  (let ((tester
         (mp:process-run-function "Test reading entries from file" ()
                                  'flush-entries-from-file
                                  *test-file*
                                  *standard-output*)))
    ;; Use the previous example to create test data for this test.
    (test-flush-entries-to-log-file)
    (mp:process-kill tester)))

Although both of these examples were somewhat contrived, note their intentions to preserve their data from external modification once the waiting threads have woken up. In the first example, shiftf is used to atomically retrieve a symbol-value and reset it. In the above examples, this level of care didn't particularly matter, but in some applications it may be important to keep threads from trampling on each other's data.

Note also the use of mp:process-kill to terminate unwanted threads when each test is complete.

Exercise: Create and test a thread which will wait until the symbol foo is boundp and then announce the fact.

Per-thread state

Every thread in your lisp system has its own execution stack and hence its own private state. The following aspects of state will therefore vary between different threads:

On the other hand, the following are globally set rather than bound in a lisp system and so will not vary between threads:

To see how per-thread variable bindings can lead you astray, consider the following examples:

CL-USER 34 > (defvar *foo* nil)
*FOO*

CL-USER 35 > (mp:process-run-function "" () (lambda () (setf *foo* 1)))
#<MP:PROCESS Name "" Priority 850000 State "Running">

CL-USER 36 > *foo*
1

CL-USER 37 > (mp:process-run-function
              "Bind in new thread, can't see elsewhere." ()
              (lambda ()
                (let ((*foo* 2))
                  (sleep 5)
		  (setf *foo* 3)
                  (print 'done #.*standard-output*))))
#<MP:PROCESS Name "" Priority 850000 State "Running">

CL-USER 38 > *foo*
1

DONE
CL-USER 39 > (let ((*foo* 4))
               (mp:process-run-function
                "Bind in old thread, can't see elsewhere." ()
                (lambda () (print *foo* #.*standard-output*))))
#<MP:PROCESS Name "" Priority 850000 State "Running">

1
CL-USER 40 >

In both lines 34 and 35, *foo* is globally set. This means that every thread shares the symbol's value. In line 37, *foo* is bound only within the new thread; the original thread (i.e. the listener) does not see this binding or the result of a setf within the binding. Similarly, in line 39 the binding is present only in the original thread. To create a binding in a new thread, use mp:*process-initial-bindings*:

CL-USER 40 > (let ((mp:*process-initial-bindings*
                    ;; Note the "dotted list" format - an unpleasant trap
		    ;; for the unwary. Note also that new value is pushed
		    ;; onto existing list so that system defaults aren't lost.
                    (cons '(*foo* . 5) mp:*process-initial-bindings*)))
               (mp:process-run-function
                "Bind around new thread." ()
                (lambda ()
                  (print *foo* #.*standard-output*))))
#<MP:PROCESS Name "Bind around new thread." Priority 850000 State "Running">

5
CL-USER 41 >

Exercise: Explain from examination of mp:*process-initial-bindings* why calling in-package in one listener does not affect the package in another.

Mailboxes

The mailbox is a structure designed to facilitate the transfer of data between threads. A number of operations are defined on mailboxes and are guaranteed to be thread safe - that is, different threads can invoke any number of these operations "at the same time" without corrupting the mailbox structure.

The following example uses mailboxes to transfer "data" from ten "generating" threads to one "processing" thread. The function mp:mailbox-send takes a mailbox and an arbitrary lisp object as its arguments. Objects sent to the mailbox are queued in FIFO (first in, first out) order, and are retrieved by calling mp:mailbox-read. Note that this this function will hang if the mailbox is empty when it is invoked; the wait reason and timeout are optional arguments. Note also how the timeout is used here to terminate the "processing" thread after the data have stopped arriving.

(in-package "CL-USER")

;; process-data creates a thread which watches for data being
;; sent to its mailbox and then "processes" them.
(defun process-data (ostream)
  (let ((mailbox))
    (mp:process-run-function
     "Process data" ()
     (lambda ()
       ;; Create a mailbox
       (setf mailbox (mp:make-mailbox))
       (loop
        ;; Wait for someone to write to the mailbox
        (let ((datum (mp:mailbox-read mailbox
                                      "Waiting for data to process"
                                      5)))
          ;; "Process" the result
          (if datum
              (format ostream "~&Processing ~a.~%" datum)
            ;; Looks like everyone else went away. Terminate self.
            (return))))))
    ;; See the Slightly Tougher Exercise below...
    (mp:process-wait "Waiting for mailbox to exist."
                     (lambda () mailbox))
    ;; Return mailbox so that others can share it.
    mailbox))

;; generate-data is called by each "generator" thread, to send
;; 100 data to the mailbox. Each thread will die when its call to this
;; function returns.
(defun generate-data (id mailbox)
  (loop for count to 100 do
        (let ((datum (cons id count)))
          (sleep (random 1.0))
          (mp:mailbox-send mailbox datum))))

;; Pass mailbox from "processor" to various "generators".
(defun mailbox-demo ()
  (let ((mailbox (process-data *standard-output*)))
    (loop for id to 10 do
          (mp:process-run-function
           (format nil "Generator ~d." id) ()
           'generate-data
           id
           mailbox))))

Slightly Tougher Exercise: Consider the call to mp:process-wait in the example above. Explain what will happen -- and why -- if (a) the call is removed and (b) the call is replaced by:

    (mp:process-wait "Waiting for mailbox to exist."
                     'identity
		      mailbox)

Interrupts

The above sections have dealt with ways of using data to communicate between threads. Another important mode of communication is the interrupt: a message sent by one thread to another, to tell that thread to stop whatever it was doing and get on with something else instead. This might come in handy if:

To interrupt a thread, use the function mp:process-interrupt. This takes a thread as its first argument, followed by a function and optionally arguments to that function. As elsewhere, you can use mp:find-process-from-name to locate the thread object corresponding to a given name. For example:

(defun flush-cache (cache)
  (let ((tcp-server (mp:find-process-from-name "Port 80 server")))
    (mp:process-interrupt tcp-server
                          'flush-data-cache
			  cache)))

A related utility is mp:process-interrupt-list, which takes just three arguments: a thread, a function, a list of arguments to that function.

Exercise: Use mp:*process-initial-bindings* to create a thread whose *standard-output* is the listener you're working in. Set this thread to sleeping for a long time (1000 seconds will do). Now create a second thread without the redirected *standard-output*, and have it ask the sleeper to print something for it.

Threads and the CAPI windowing system

By default, every CAPI interface runs in its own thread. This thread will be used by the lisp system for actions such as redisplay and invocation of callbacks. If you need to act programmatically on a CAPI interface, you are strongly recommended to work in the appropriate thread.

The utility capi:execute-with-interface will help you out here. This takes as its first argument a CAPI interface; subsequent arguments are a function followed by optional arguments to that function. The function will be invoked in the thread belonging to that interface. To get from any CAPI element to its interface, use the reader capi:element-interface, as in the following example.

In this example, the only way to get *switchable* to switch is to execute the request in *switchable*'s thread.

;; Create and display a window which can switch between
;; two children. These have different coloured backgrounds.
;; Red was listed first and so by default is visible initially.
(defvar *switchable*
  (let ((red-pane (make-instance 'capi:output-pane
                                 :name 'red
                                 :background :red))
        (green-pane (make-instance 'capi:output-pane
                                   :name 'green
                                   :background :green)))
    (capi:contain
     (make-instance 'capi:switchable-layout
                    :description (list red-pane green-pane)))))

;; Utility to return the green child.
(defun green-pane (switchable)
  (find 'green (capi:switchable-layout-switchable-children
                switchable)
        :key 'capi:capi-object-name))

;; If you try this then (a) you'll get an error and (b) calling
;; (right *switchable*) won't help - that window's state is broken
;; and you'll have to create a new one.
(defun wrong (switchable)
  (setf (capi:switchable-layout-visible-child switchable)
        (green-pane switchable)))

(defun right (switchable)
  (capi:execute-with-interface
   (capi:element-interface switchable)
   (lambda (switchable)
     (setf (capi:switchable-layout-visible-child switchable)
           (green-pane switchable)))
   switchable))

Locks

Sometimes it's important to control access to some resource, so that only one thread is operating on it at a time. One method of effecting this is to restrict access from your code to that resource to one (or more) specialised threads, and to have other threads write to a mailbox when they want the specialised threads to access the resource on their behalf. However there are two potential drawbacks to this approach.

A lock is a simple lisp object, which can be held by no more than one thread at a time. A thread attempting to hold a lock which is already in use will hang (i.e. be forced to wait) until the lock is freed. In the following example, the locking mechanism is reduced to its most minimal form.

;; Create a lock and remember it.
(defvar *lock* (mp:make-lock))

(defun use-resource-when-free (id stream)
  ;; Callers must wait at this point for the lock to
  ;; be freed, before they can proceed with the body of
  ;; this form.
  (mp:with-lock (*lock*)
    (use-resource-anyway id stream)
    ;; When we exit the form, the lock is freed automatically
    ;; and some other thread will be allowed to claim it.
    ))

(defun use-resource-anyway (id stream)
  (format stream "~&Starting ~a." id)
  (sleep 1)
  (format stream "~&Ending ~a." id))

(defun test (lock-p)
  (let ((run-function (if lock-p
                          'use-resource-when-free
                        'use-resource-anyway)))
    (dotimes (id 3)
      (mp:process-run-function
       (format nil "Competing thread ~a" id) nil
       run-function id *standard-output*))))

A brief warning about timers

Timers are a handy way of notifying your application that some period of time has elapsed. You should however be aware of the following vital piece of information: TIMERS RUN IN WHATEVER PROCESS IS RUNNING WHEN THE TIME IS REACHED, WHICH IS LIKELY TO BE THE IDLE PROCESS IN CASES WHERE LISPWORKS IS SLEEPING...

CL-USER 62 > (defun foo () (print mp:*current-process*
                                  #.*standard-output*))
FOO

CL-USER 63 > (setf *timer* (mp:make-timer 'foo))
#<Time Event : FOO>

CL-USER 64 > (mp:schedule-timer-relative *timer* 1 1)
#<Time Event : FOO>

#<MP:PROCESS Name "The idle process" Priority -8388608 State "Running">
#<MP:PROCESS Name "The idle process" Priority -8388608 State "Running">
#<MP:PROCESS Name "The idle process" Priority -8388608 State "Running">
#<MP:PROCESS Name "The idle process" Priority -8388608 State "Running">
CL-USER 65 > (mp:unschedule-timer *timer*)
#<Time Event : FOO>

CL-USER 66 >

A consequence of this is that any errors you get in a timer are errors in the idle process, which is the manager for multiprocessing and generally a Bad Place to Get Errors. This was potentially a more serious problem in LispWorks 4.1; it looks safer (i.e. non fatal) in more recent editions of LispWorks. In any case, you are advised always to switch to another process to run the contents of your timer.

(defvar *timer*)
(defparameter *housekeeping-interval* 3600)      ; once per hour

;; Create a timer, set it to "expire" in one hour, and subsequently at
;; hourly intervals.
(defun start-timer ()
  (setf *timer* (mp:make-timer 'safe-housekeeping))
  (mp:schedule-timer-relative *timer*
                              *housekeeping-interval*
			      *housekeeping-interval*))

; Switch thread, so we can't possibly get errors in the idle process ;-(
(defun safe-housekeeping ()
  (mp:process-run-function "Housekeeping" nil
                           'housekeeping))

Initializing multithreading

As noted previously, multithreading is "always" running in the LispWorks for Windows environment. However, while the image is initializing itself (loading your ".lispworks" file, for example, or running a :restart-function supplied to save-image), multithreading has not yet started and so "inter-thread" functions such as mp:process-run-function and mp:process-wait cannot be called.

There are circumstances in which you will want to start multithreading yourself:

If in doubt, you can tell whether multithreading has started yet by examining either mp:*current-process* or (mp:list-all-processes). If either of these is null, multithreading hasn't started.

To start multithreading, call (mp:initialize-multiprocessing). Specify the threads that should run on startup of multithreading via the variable mp:*initial-processes*. This is a list whose members are themselves lists to which mp:process-run-function can be applied. Warning: do not remove any entries which the implementation has placed on mp:*initial-processes*.

Note that (mp:initialize-multiprocessing) does not return until all threads have run to completion, at which point multithreading itself halts. If you call it from the non-windowing listener without specifying any threads to run, (mp:initialize-multiprocessing) will appear to return no values immediately, but appearances can be deceptive - what actually happens is that LispWorks is designed to spot that multithreading has been started without any threads to run, and so to give you a "default listener" (in its own thread) rather than leaving you with a hung lisp.

The above ideas are illustrated by the following example. To run this on Windows or Linux, you will need to resave the image to run in the TTY (without multithreading on startup), by passing the keyword argument :environment nil to save-image.

/cygdrive/c/Program Files/Xanalys/LispWorks $ ./console-test.exe
LispWorks(R) (for the Windows(R) operating system)
Copyright (C) 1987-2002 Xanalys Inc.  All rights reserved.
Version 4.2.6
Saved by nick as console-test, at 28 Jun 2002 15:25
User nick on GANNET
; Loading text file c:\Program Files\Xanalys\LispWorks\lib\4-2-0-0\config\siteinit.lisp
;  Loading text file c:\Program Files\Xanalys\LispWorks\lib\4-2-0-0\private-patches\load.lisp
; Loading text file e:\.lispworks

CL-USER 1 > mp:*current-process*

NIL

CL-USER 2 > (push (list "My only thread" ()
			(lambda ()
			  (print (mp:list-all-processes) *terminal-io*)
			  (sleep 5)))
		  mp:*initial-processes*)

(("My only thread" NIL #'(LAMBDA NIL (PRINT (MP:LIST-ALL-PROCESSES) *TERMINAL-IO*) (SLEEP 5)))
 ("The idle process" (:PRIORITY -8388608 :RESTART-ACTION :CONTINUE) MP::PROCESS-IDLE-FUNCTION))

CL-USER 3 > (mp:initialize-multiprocessing)

(#<MP:PROCESS Name "My only thread" Priority 0 State "Running">
 #<MP:PROCESS Name "The idle process" Priority -8388608 State "Running">)
NIL

CL-USER 4 > (mp:list-all-processes)

NIL

CL-USER 5 > (pop mp:*initial-processes*)

("My only thread" NIL #'(LAMBDA NIL (PRINT (MP:LIST-ALL-PROCESSES) *TERMINAL-IO*) (SLEEP 5)))

CL-USER 6 > (mp:initialize-multiprocessing)


CL-USER 7 > (mp:list-all-processes)

(#<MP:PROCESS Name "default listener process" Priority 600000 State "Running">
 #<MP:PROCESS Name "The idle process" Priority -8388608 State "Running">)

CL-USER 8 > (quit)
/cygdrive/c/Program Files/Xanalys/LispWorks $

Exercise: Resave a lisp image which will restart with a "TTY" listener running in multithread mode.


Copyright © 2002-2007 The Common Lisp Cookbook Project
http://cl-cookbook.sourceforge.net/
Valid XHTML 1.0!

$Header: /cvsroot/cl-cookbook/cl-cookbook/process.html,v 1.16 2007/01/16 20:58:32 ozten Exp $