Ken Wesson <kwess...@gmail.com> writes:

> Retries? We were discussing an atom here, not a ref. A dosync
> transaction may be retried. The obvious implementation for swap! would
> be (locking inner_value (set! inner_value.contents (apply fun
> inner_value.contents))) (warning: pseudocode, I assume this stuff
> would actually be implemented in Atom.java or someplace like that in
> Java rather than in Clojure).

Sorry, I didn't explain very well.  I basically said that it's not
implemented with locks, rather than how it *is* implemented.  I should
have pointed at the Clojure documentation:

    "Internally, swap! reads the current value, applies the function to
    it, and attempts to compare-and-set it in. Since another thread may
    have changed the value in the intervening time, it may have to
    retry, and does so in a spin loop. The net effect is that the value
    will always be the result of the application of the supplied
    function to a current value, atomically. However, because the
    function might be called multiple times, it must be free of side
    effects."

    -- http://clojure.org/atoms

The actual implementation from clojure/lang/Atom.java is quite concise
too:

    public Object swap(IFn f) throws Exception{
      for(; ;) {
        Object v = deref();
        Object newv = f.invoke(v);
        validate(newv);
        if(state.compareAndSet(v, newv)) {
          notifyWatches(v, newv);
          return newv;
        }
      }
    }

> (let [owned (Object.)]
>   (swap! the-atom #(if (= % sentinel) owned %))
>   (if (= @the-atom owned)
>     (reset! the-atom (do-once-fn))
>     (loop []
>       (if (= (.getClass @the-atom) Object)
>         (do
>           (Thread/sleep 10)
>           (recur))
>         (@the-atom)))))
>
> The interesting bit here deals with the case that the atom holds
> someone else's "owned" object instead of an actual result. The loop
> sleeps 10ms and checks repeatedly until the do-once-fn (running in
> some other thread) has completed and the atom's final value been
> assigned. It does assume that the result of do-once-fn will not be an
> unspecialized java.lang.Object.
>
> The locking version is probably cleaner than the above, but when no
> return value is needed, the code further above is short and especially
> concise; both are lock-free (given that swap! and reset! are
> lock-free, which, if they perform retries, is presumably the case).

You've actually implemented a polling sleeplock in terms of an atom.
Using the language's own lock has the benefit that long sleeping threads
do not have to keep polling and using CPU time, the OS scheduler will
wake them up when the lock is released.

Locking is semantically correct for this situation.  Imagine the webapp
case, you start up the webapp and want to initialize something on 
the first request.  What happens if 3 requests come in simultaneously
and all hit your initialize?  You want two of the threads to block and
wait while one of them does the initialization.  That's locking (mutual
exclusion).

>> Atoms do not lock.  If multiple threads enter swap! simultaneously, they
>> *will* each execute f.  One will win and the others will retry.
>
> Eh. Why this implementation? To optimize for the common case that
> there isn't any contention?

CAS is a lower-level primitive, locks and semaphores can be
implemented using a CAS as you demonstrated. ;-)

But yes, there are potential performance benefits, depending on
contention.  For example, if f does some calculation and ends up often 
returning the value unchanged then multiple threads can do that
simultaneously without blocking.

Also context switching (due to blocking on a lock) is quite expensive.
However, if there's really high contention and a lot of retrying, 
locking may be better from a performance perspective as it allows the
blocked thread to sleep and other tasks to run.

Finally, the atom is a safer concurrency primitive, it's impossible to
deadlock (unless you implement a lock with one, of course).  

Further reading about CAS:

http://en.wikipedia.org/wiki/Compare-and-swap
http://en.wikipedia.org/wiki/Non-blocking_algorithm

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to