wiki:ThreadsAndSetf

Version 1 (modified by alms, 6 years ago) (diff)

--

> Suppose we have two threads that are both doing
>
>   (setq *x* <form>)
>
> where *x* is a global special variable not bound in either thread.
> Is this setq atomic?  That is, can we rely on the assertion
> that after both setqs have been executed, the end result is either
> that *x* is set to the value of the <form> in the first thread,
> or the value of the <form> in the second thread?  
> Or do we need a lock here?

The value will be one or the other of the values assigned
in the two forms, but which of those two values isn't generally
predictable. You won't get some bits from the first thread's
value and some bits from another thread's value, or a third
value or anything like that, but after a thread does:

(setq *x* value)
(eq *x* value) might be true or false

> Same question, only this time instead of setq'ing a special,
> we do
>
> (setf (x y) <form>)
>
> where x is a CLOS accessor function to a CLOS slot
> that is declared :allocation :class.  Do we need a lock
> here?

It's basically the same answer: value "b" might be stored
in that slot by thread B at about the time that thread A stores
value "a" there; one of the threads will win, but you can't
generally predict which one.

Locking isn't necessary at the GC-level: some value that had
been stored into the variable or class-allocation slot will
be visible to the GC, and it shouldn't care if another value
was there a cycle or two earlier.

Locking's very often necessary at the application level,
since without it it's very easy to obtain anomalous results.

Suppose that an application had a couple of global variables:

(defvar *number-of-requests-received* 0)
(defvar *number-of-requests-completed* 0)

and that whenever some thread received or completed a request
it used INCF to increment the value of the global variable by 1.
If we don't use locking around those INCFs, it's possible for
the value of either variable to decrease when written to, and
we might find that the number of completed requests exceeds the
number of requests received and couldn't really trust either
number.

There are other, more subtle issues as well: a modern processor
achieves a lot of performance gain by reordering memory accesses
(sometimes doing things like combining 2 64-bit writes to
adjacent memory locations into a single 128-bit write, for example).
This is usually done in a way that's transparent to the program, so
in the case where something is only written to by 1 thread,

(progn
  (setq *x* 1)
  *x*)

the value 1 will be returned.  That value may not really be in memory
(or anywhere that triggers cache coherency protocols) yet, so the value
of *x* visible to another thread running on another CPU may not match
the value written until several cycles have elapsed.  (So you can't
generally use a shared global value for precise synchronization between
threads, unless you also force memory synchronization to occur.)