On Wed, Dec 1, 2010 at 12:55 AM, Alex Osborne <a...@meshy.org> wrote:
> Ken Wesson <kwess...@gmail.com> writes:
>
>> I put a println in a swap! function once and the world didn't end.
>
> For a println retries are usually harmless

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).

> it just means you'll
> occasionally get doubled output.  But it's not a good thing for a
> general-purpose runonce fn whose entire reason for existence is to
> ensure something is called only once. ;-)

If swap! does sometimes run its argument twice then you'd need
something slightly more sophisticated, like:

(let [owned (Object.)]
  (swap! the-atom #(if (= % sentinel) owned %))
  (when (= @the-atom owned)
    (do-once-fn)
    (reset! the-atom nil)))

Here, the swap changes the atom to the unique object "owned" if and
only if it's still holding the sentinel value. If afterward the atom's
value is owned, we've claimed ownership of the atom and we call
do-once-fn. If anyone else beat us to the punch the atom will hold
either a different "owned" object or else nil.

This works if we're not actually interested in the return value of the
function, just in making it execute exactly once. If we need the
result:

(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).

> The guarantee of atomicity is *dependent* on the function not having
> side-effects.

How so? (Other than that it would probably be bad to reference the
atom explicitly inside of the swap! function.)

> 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?

-- 
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