In your example transducer, the problem is with the `result` parameter. The
specification of transducers is that the result of `(rf result x)` should
be fed into the next call to `rf`. In other words: (-> result (rf x1) (rf
x2) (rf x3))` trying to do that in a parallel context is next to
impossible. Not saying there isn't a way to code a transducer-like thing to
work with multiple threads, but the result of that would look a lot more
like core.async or Reactive Extensions, than the transducers we have today.

On Sun, Apr 9, 2017 at 4:57 PM, Alexander Gunnarson <
alexandergunnar...@gmail.com> wrote:

> That makes sense about them not being designed for that use case. I would
> add, though, that transducers could certainly be used in a parallel context
> *if* the current transducer implementations were abstracted such that you
> could pass internal state generator and modifier functions and use the
> correct ones in whichever context is appropriate (single-threaded
> read/write, sequentially multi-threaded read/write à la core.async,
> concurrently multi-threaded read/write à la core.reducers). In the case of
> `map-indexed`, the fact that its transducer uses a volatile as currently
> implemented is not part of the `map-indexed` "contract", if you will, and
> seems to me to be an implementation detail. One could just as easily write
> the transducer for `map-indexed` as below:
>
> (defn map-indexed-transducer-base [f box-mutable inc-mutable]
>   (fn [rf]
>     (let [i (box-mutable -1)]
>       (fn
>         ([] (rf))
>         ([result] (rf result))
>         ([result input]
>           (rf result (f (inc-mutable i) input)))))))
>
> (defn map-indexed-transducer-single-threaded [f]
>   (map-indexed-transducer-base f unsynchronized-mutable-long! 
> #(unsynchronized-mutable-swap!
> % inc))
>
> (defn map-indexed-transducer-sequentially-multi-threaded [f]
>   (map-indexed-transducer-base f volatile! #(vswap! % inc))
>
> (defn map-indexed-transducer-concurrently-multi-threaded [f]
>   (map-indexed-transducer-base f atom #(swap! % inc)) ; or an AtomicLong
> variant
>
>
> On Sunday, April 9, 2017 at 6:47:46 PM UTC-4, tbc++ wrote:
>>
>> Transducers were never designed to work in parallel context. So I'd
>> define any behavior that arises from using the same transducers in multiple
>> threads *at the same time*, as undefined behavior.
>>
>> On Sun, Apr 9, 2017 at 4:39 PM, Alexander Gunnarson <
>> alexander...@gmail.com> wrote:
>>
>>> 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/mai
>>>>> n/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 clo...@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+u...@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+u...@googlegroups.com.
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>
>>
>>
>> --
>> “One of the main causes of the fall of the Roman Empire was that–lacking
>> zero–they had no way to indicate successful termination of their C
>> programs.”
>> (Robert Firth)
>>
> --
> 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.
>



-- 
“One of the main causes of the fall of the Roman Empire was that–lacking
zero–they had no way to indicate successful termination of their C
programs.”
(Robert Firth)

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