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.

Reply via email to