Changes between Initial Version and Version 1 of ThreadsAndSetf

06/05/08 21:40:32 (9 years ago)



  • ThreadsAndSetf

    v1 v1  
     2> Suppose we have two threads that are both doing 
     4>   (setq *x* <form>) 
     6> where *x* is a global special variable not bound in either thread. 
     7> Is this setq atomic?  That is, can we rely on the assertion 
     8> that after both setqs have been executed, the end result is either 
     9> that *x* is set to the value of the <form> in the first thread, 
     10> or the value of the <form> in the second thread?   
     11> Or do we need a lock here? 
     13The value will be one or the other of the values assigned 
     14in the two forms, but which of those two values isn't generally 
     15predictable. You won't get some bits from the first thread's 
     16value and some bits from another thread's value, or a third 
     17value or anything like that, but after a thread does: 
     19(setq *x* value) 
     20(eq *x* value) might be true or false 
     22> Same question, only this time instead of setq'ing a special, 
     23> we do 
     25> (setf (x y) <form>) 
     27> where x is a CLOS accessor function to a CLOS slot 
     28> that is declared :allocation :class.  Do we need a lock 
     29> here? 
     31It's basically the same answer: value "b" might be stored 
     32in that slot by thread B at about the time that thread A stores 
     33value "a" there; one of the threads will win, but you can't 
     34generally predict which one. 
     36Locking isn't necessary at the GC-level: some value that had 
     37been stored into the variable or class-allocation slot will 
     38be visible to the GC, and it shouldn't care if another value 
     39was there a cycle or two earlier. 
     41Locking's very often necessary at the application level, 
     42since without it it's very easy to obtain anomalous results. 
     44Suppose that an application had a couple of global variables: 
     46(defvar *number-of-requests-received* 0) 
     47(defvar *number-of-requests-completed* 0) 
     49and that whenever some thread received or completed a request 
     50it used INCF to increment the value of the global variable by 1. 
     51If we don't use locking around those INCFs, it's possible for 
     52the value of either variable to decrease when written to, and 
     53we might find that the number of completed requests exceeds the 
     54number of requests received and couldn't really trust either 
     57There are other, more subtle issues as well: a modern processor 
     58achieves a lot of performance gain by reordering memory accesses 
     59(sometimes doing things like combining 2 64-bit writes to 
     60adjacent memory locations into a single 128-bit write, for example). 
     61This is usually done in a way that's transparent to the program, so 
     62in the case where something is only written to by 1 thread, 
     65  (setq *x* 1) 
     66  *x*) 
     68the value 1 will be returned.  That value may not really be in memory 
     69(or anywhere that triggers cache coherency protocols) yet, so the value 
     70of *x* visible to another thread running on another CPU may not match 
     71the value written until several cycles have elapsed.  (So you can't 
     72generally use a shared global value for precise synchronization between 
     73threads, unless you also force memory synchronization to occur.)