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