I should add that, as Timothy pointed out, if multiple threads mutate and read the value but only one ever does so at a time, as is the case in `core.async`, then a volatile is sufficient. My preliminary conclusions above about volatiles apply only to concurrent mutation via e.g. `fold` or the like.
Also, regarding the locks you mentioned, Seth, I read up a little on the Java memory model here <http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#synchronization> and I can confirm that a lock is sufficient to provide *both* write *and* read thread-safety guarantees: ... acquir[ing a] monitor ... has the effect of invalidating the local > processor cache so that variables will be reloaded from main memory. We > will then be able to see all of the writes made visible by the previous > release. > `Volatile` only provides a subset of these read-safety guarantees, so a `volatile` in addition to a lock is indeed overkill, if that's what is happening. On Sunday, April 9, 2017 at 6:19:51 PM UTC-4, Alexander Gunnarson wrote: > > It looks that way to me too, Seth, though I'd have to comb over the > details of the locks implemented there to give a reasoned opinion of my > own. But yes, if that's the case, the volatile isn't adding anything. > > Anyway, I'm not trying to poke holes in the current implementation of > transducers — on the contrary, I'm very appreciative of and impressed by > the efforts the clojure.core (and core.async) contributors have made on > that and other fronts. Transducers are an extremely powerful and elegant > way to express code that would otherwise be a lot more complex and > difficult to reason about. I'm just trying to figure out where I can get > away with having unsynchronized mutable versions of stateful transducers > that currently use volatiles, and where I need even stronger measures of > thread safety than volatiles. > > To take these thoughts further, I did a simple test to compare the three > types of mutability we've been talking about (unsynchronized, volatile, and > atomic — I can reproduce the code here if you'd like) and the takeaway is > that `map-indexed` really does rely on atomic operations in a multithreaded > context, as each index depends on the previous index value. When doing a > `volatile`-based `map-indexed` in parallel on a small collection (8 > elements), the `volatile` value stays consistent — that is, all the correct > indices are passed to the mapping function. However, over a sufficiently > large collection (100 elements, though it could happen on smaller scales > too), the `volatile` value starts to break down: duplicate index values are > passed to the mapping function and the highest index value only ever > reaches 97 at the maximum. The same phenomenon happens, of course, with the > unsynchronized-mutable-box-based `map-indexed`, though it happens at a > small scale too (calling the unsynchronized `map-indexed` on 8 elements > operated on by 2 threads produces only 7 unique indices). > > My preliminary conclusions are: > - Unsynchronized mutability is fine in contexts known to be only > single-threaded, in which I could replace the `volatile` in `map-indexed` > and other transducers with unsynchronized mutable boxes. > - Volatiles are good when all you want to do is set the value and have > multiple threads always read the most up-to-date value, without having to > depend on a previous value via e.g. `inc`. > - Atomic boxes (`atom`, `AtomicLong`, etc.) are necessary when the mutable > value relies on the previous value via e.g. `inc`, as is the case with > `map-indexed`. > > My guess is that all this applies to e.g. the unsynchronized `ArrayList` > in `partition-by` as well, which might need to be a synchronized collection > or an immutable one boxed in an atom, but I haven't tested this. > > Would you agree with these conclusions, Seth and Timothy? > > On Sunday, April 9, 2017 at 1:56:38 PM UTC-4, Seth Verrinder wrote: >> >> I'll defer to Timothy on the particulars of core.async but it looks like >> [1] the transducer in channel is protected by a lock. If that's the case >> volatile isn't adding anything in terms memory barriers. >> >> 1: >> https://github.com/clojure/core.async/blob/master/src/main/clojure/clojure/core/async/impl/channels.clj#L71 >> >> On Sunday, April 9, 2017 at 11:58:00 AM UTC-5, Alexander Gunnarson wrote: >>> >>> Thanks so much for your well-considered reply, Timothy! That makes sense >>> about volatiles being used in e.g. core.async or core.reducers contexts >>> where the reducing function that closes over the mutable value of the >>> stateful transducer is called in different threads. Why, then, are >>> unsynchronized ArrayLists used e.g. in 'partition-by'? It's also closed >>> over by the reducing function in just the same way as the volatile long >>> value internal to e.g. 'map-indexed'. I'm not yet clear on how one (the >>> ArrayList) is acceptable being non-volatile and the other (the volatile >>> long) is unacceptable. When .add is called, an unsynchronized mutable >>> counter is updated so the ArrayList can insert the next value at the >>> correct index. Do you have any insight into this? Meanwhile I'll go do some >>> digging myself on the Clojure JIRA etc. so I'm more informed on the >>> subject. >> >> -- 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 --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.