if you do it as a lock, then readers must block writers (think it
through). Clojure's reference types + immutable datastructures and the
views on perception that underlay them are strongly opposed to readers
interfering with writers.
(def a (atom {}))
(defn add-kv [k v]
(swap! assoc k v))
If I call add-kv from multiple threads, how can I assume that the map
won't be modified in the middle of the assoc? Sure, I could lock, but
read this article first:
http://en.wikipedia.org/wiki/Non-blocking_algorithm
The idea is that with atoms there will always be some progress. Even
if one updater takes an hour to complete, the other threads can
continue as needed. True that single thread will not complete until
there is a 1 hour window, but that's where other synchronization
methods come into play (worker queues, for example).
Timothy
Finish the thought, what happens when there is "contention", a thread
reads then writes before you acquire the lock to commit. You can try
and making locking work, but you'll just up with CAS based on a lock
The "making progress" seems an illusion here to me. Sure, you can make progress in one thread while another thread is taking one hour to finish its part. But the cost is the "long" thread finally found out "oops, I have to start over my one-hour job".
Being lockless seems useful for certain cases (like real-time system as mentioned in the Wikipedia article). But I still could not grasp the idea how it can increase *real* work throughput, as the problem itself mandates a part of the work can only be done in serial.
I have a hard time understanding why there is a need to retry when doing "swap!" on an atom. Why does not Clojure just lock the atom up-front and do the update? I have this question because I don't see any benefit of the current "just try and then re-try if needed" (STM?) approach for atom (maybe OK for refs because you cannot attach a lock to unknown ref combinations in a "dosync" clause). Right now I have an atom in my program and there are two "swap!" functions on it. One may take a (relatively) long time, and the other is short. I don't want the long "swap!" function to retry just because in the last minute the short one sneaked in and changed the atom value. I can do the up-front lock myself, but I wonder why this is not already so in the language. Thank you for any enlightenment.
--
I have a hard time understanding why there is a need to retry when doing "swap!" on an atom. Why does not Clojure just lock the atom up-front and do the update? I have this question because I don't see any benefit of the current "just try and then re-try if needed" (STM?) approach for atom (maybe OK for refs because you cannot attach a lock to unknown ref combinations in a "dosync" clause). Right now I have an atom in my program and there are two "swap!" functions on it. One may take a (relatively) long time, and the other is short. I don't want the long "swap!" function to retry just because in the last minute the short one sneaked in and changed the atom value. I can do the up-front lock myself, but I wonder why this is not already so in the language. Thank you for any enlightenment.
On a x86 machine the swap basically compiles down to a single assembly
code instruction:
http://jsimlo.sk/docs/cpu/index.php/cmpxchg.html
On a normal x86 machine, every lock in the system will boil down to
using this single instruction. x86 has no concept of "locks". Locks
are simply a construct created by the operating system that is
implemented with a series of cmpxchg instructions. This is the reason
this instruction exists in the first place. Every type of
lock/semephore/mutex we need in a operating system can be built off of
this single instruction. This is also why Clojure includes atoms.
> In addition, most systems only support loading memory in cache lines.The cache line size on x86 is 32 bytes on 32 bit systems, not 16KB. On
> IIRC, today most cache lines are 16KB. So when you read a single byte,
> the 16KB around that memory location is loaded as well.
64 bit systems, it's 64 bytes.
> So there's a odd side-effect here. Notice how it locks a whole cacheSort of, but this false sharing and subsequent performance degradation
> line (16KB)? This means that if you allocated 4K atoms from the same
> cache line, swapping one would cause the others to lock during the
> CAS.
happens whether or not you use CAS to access the items in a cache
line.