On Fri, Jul 29, 2011 at 3:19 PM, Julien <julien.c.chast...@gmail.com> wrote:
> This listing is an attempt to make the function safe for concurrent
> modification. My claim is that count and seq should also be locking
> around "a" for exactly same reason as aget and aset. In particular,
> locking is not only ensuring mutual exclusion but also *visibility*.
> The danger is that count and seq may be accessing stale values of "a".

In the case of "count" that danger doesn't exist; Java arrays are of
fixed size from creation, so the "count" value cannot change.

The case of "seq" is trickier: the array can indeed change contents
midway through iteration. But there's no simple fix. One could write
something like

(let [cnt (dec (count a))
      step (fn step [i]
             (lazy-seq
               (if (> i cnt)
                 (do (monitorexit a) nil)
                 (cons x (step (inc i))))))]
  (monitorenter a)
  (step 0))

to hold a lock all the way through seq traversal, but if the seq is
only partially traversed (either deliberately, or because of a thrown
exception) the lock will never get released.

More complicatedly, you could create a CloseableSeq defprotocol that
extends ISeq and Closeable and create closeable seqs (line-seq could
benefit from this as well); for the above the close operation would be
(monitorexit a) (and for line-seq (.close rdr)). Closeable seqs could
then be created in (with-open ...) and handled in an exception-safe
manner -- so long as you made sure to consume or doall the seq before
the end of the with-open scope. In the array seq case, the seq would
still seem to work after the scope exited, but it would show an array
in a possible state of flux again instead of a fixed state.

Given all this complication, easiest might be

(seq [this]
  (locking a
    (seq (into [] a))))

which has the downside of copying the array into a vector but the
upside of being thread- and exception-safe; the seq is backed by a
fixed snapshot of the array. And can be traversed lazily and at
leisure without holding onto a lock the whole time; the lock's held
only during the copying. If you're worried about seeing inconsistent
cell values during a traversal of a short array this is what you ought
to use.

But you should probably prefer to avoid arrays for the most part. :)

Note: you will still potentially see *stale* values; the array can
change after the snapshot is made. To avoid that you'd always have to
lock during the operation where you want to be seeing the most up to
date values. But you wouldn't see *inconsistent* values. If the array
contains all 1s, and then someone else locks it, changes each cell to
2, and unlocks, and you are traversing the seq given above, you might
get back (1 1 1 1 2 2 2 ...) which violates the intended invariant of
all-the-same-number. With the snapshot you'll possibly see all-1s even
after it's been updated to all-2s but you will never see a mixture.
With locking the array the whole time you're working with the seq,
you'll see all-2s if the array was ever updated to all-2s and
otherwise the thing that will eventually update it to all-2s will
block until you're done with the seq.

> See Java Concurrency in Practice, Section 3.1 for more of a
> discussion. My reasoning is based on the assumption that locking is
> simply an abstraction for the Java synchronized keyword. That
> assumption may or may not be correct.

It is correct, though Clojure exposes the lower-level monitorenter and
monitorexit; both Java synchronized and Clojure locking boil down to
(the bytecode emitted by)

(try
  (monitorenter foo)
  ...
  (finally (monitorexit foo)))

so as to ensure the lock is released even if an exception is thrown
out of the critical section. Java just doesn't let you get at the
separate lock and unlock operations for normal objects (but there are
the java.util.concurrent lock object classes).

-- 
Protege: What is this seething mass of parentheses?!
Master: Your father's Lisp REPL. This is the language of a true
hacker. Not as clumsy or random as C++; a language for a more
civilized age.

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