[Openmcl-devel] threads and special variables

Gary Byers gb at clozure.com
Wed Jan 12 22:21:26 UTC 2005

The terminology here is somewhat arbitrary, but hopefully this explanation
will make sense.

A SPECIAL BINDING is an association between a special variable (or the
symbol which names that special variable) and a value. (For the sake
of argument, consider "unbound variables" to have a distinguished
value, and it's an error to access this value.)

Assignment (SETQ ...) and reference (SYMBOL-VALUE, explicitly or not)
affect/are affected by the CURRENT BINDING of a special variable. At
any point in time, exactly one binding of any special variable is current.

Binding constructs (like LET/LET*/LAMBDA/PROGV ...) establish DYNAMIC
BINDINGS for special variables.  Those bindings are current for the
extent of the binding construct.  Usually, the process of establishing
a dynamic binding is coupled with assignment of an initial value, but
those are two distinct operations (first a binding is established -
"made current" - then a value is assigned to the variable in that binding.)

If no dynamic binding is current for a special variable, the global
or static binding is current.  Things like DEFVAR/DEFPARAMETER are
usually executed in an environment where no dynamic bindings are in
effect and so usually set values relative to a variable's static

Hopefully, this matches people's understanding of how special
variables work; so far, we haven't said anything about how they work
with threads.  There are only a couple of things to add:

1) Dynamic bindings are private to a thread.

2) When a thread first begins execution, it has no dynamic bindings.

As far as I know, every CL implementation that offers threads follows
this model.

One consequence of this model is that the static bindings of all
special variables are current for any and all threads that do not
have dynamic bindings of a particular variable (e.g., "the static
binding environment is shared".)  This can lead to unexpected
results if any thread modifies values in those static bindings
while other threads are referencing those values (or also modifying
them.)  Static bindings are basically shared resources, concurrent
modification of/access to them may need to be protected by locking.

A more pleasant consequence of the model is that, once a thread has
established a dynamic binding for a special variable, it doesn't
have to worry about that variable's value changing as a result
of activity by some other thread (e.g., all that you have to do
to guard against that case is to establish a dynamic binding:

(let* ((*foo* *foo*))

no other thread can modify or access the value of *FOO* that's visible
within BODY; that presumably makes the code in BODY a lot easier to
write and understand.  (There might be some magic ways of breaking
this privacy barrier - for the benefit of debuggers - but those ways
wouldn't be generally useful.)

It sort of follows from all of this that it's awkward to use special
variables to communicate between threads, as the code in your example
tries to do.  If neither thread establishes a dynamic binding for
the variable, the static binding will be current for both threads;
a more complicated example might need a complicated locking protocol
to ensure that modifications to the shared binding are seen at the
right time.  Once either thread establishes a dynamic binding, the
threads basically have a different view of (that part of) the world;
you could use the :INITIAL-BINDINGS argument to MAKE-PROCESS to
give the second thread its own private, dynamic binding(s), but
that doesn't solve your problem.

On Wed, 12 Jan 2005, bernard tatin wrote:

> I wanted to do this :
> -----------------------------------------
> (defvar *var* nil)
> (defparameter *param* nil)
> (defun add-var (x)
>    (+ x *var*))
> (defun add-param (x)
>    (+ x *param*))
> (defun test ()
>    (format t "add-var 4 : ~a add-param 4 ~a ~%"
>            (add-var 4)
>            (add-param 4)))
> ;; it works fine
> (let ((*var* 1)
>        (*param* 2))
>    (test))
> ;; it does not work
> (let ((*var* 1)
>        (*param* 2))
>    (PROCESS-RUN-FUNCTION "test" #'test))
> -----------------------------------------
> The output is :
> -----------------------------------------
> [LeMac:cpuview] narberd% openmcl --load tests-specialvars.lisp
> ;; it works fine
> add-var 4 : 5 add-param 4 6
> ;; running the process fails
>  > Error in process test(2): value NIL is not of the expected type NUMBER.
>  > While executing: CCL::+-2
> ;;;
> ;;; #<PROCESS test(2) [Reset] #x63E5DB6> requires access to Shared
> Terminal Input
> ;;;
> Welcome to OpenMCL Version (Beta: Darwin) 0.14.2-p1 CVS ccl-0.14 20041026!
> ?
> -----------------------------------------
> I understand that *var* and *param* have their initial value (nil) when
> the process "test" starts. I try to use make-process and the key
> argument initial-binding like that :
> (make-process "test" :initial-bindings '(???))
> but I don't understand what to put in the '(???).
> Can you help me ?
> Thanks,
> Bernard.
> _______________________________________________
> Openmcl-devel mailing list
> Openmcl-devel at clozure.com
> http://clozure.com/mailman/listinfo/openmcl-devel

More information about the Openmcl-devel mailing list