>
> I could have one thread that invokes a transduce step on odd seconds and 
> another that invokes on even seconds. Or some external api call that tells 
> me to take the next step, which I do on a thread pulled from a pool.
>

Both strategies will fail to ensure no more than one thread at time. You 
need something to prevent overlapping, e.g when a long step is running and 
you get a request to start the next one.


While this is a good reference, it's also 13 years old and the JMM has been 
> updated since then. A much better reference explaining the semantics and 
> constraints is:

https://shipilev.net/blog/2014/jmm-pragmatics/ 

In particular, even if there is a memory barrier, there are some 
> reorderings allowed if the transducer state is not volatile that may be 
> surprising. Making it volatile adds a critical edge in the total program 
> order.

I'm saying that the logical ordering of steps is irrelevant wrt how a 
> multi-threaded program can be optimized/reordered under the JMM.


Thank you for the reference. Very enlightening (esp. part III 
<https://shipilev.net/blog/2014/jmm-pragmatics/#_part_iii_sc_drf>).
I understand reordering is a thing. Does ordering really matter ? What 
matters to us is that each step is able to see the changes made the step 
before. That is, we need to ensure memory visibility across steps. This is 
all what we need to be sure that the JVM won't run the program in an order 
that doesn't yield the same result as what we expect.
In a degenerate case, we'll put volatile on every variable, ensuring that 
the running program is totally ordered and totally unoptimizable. Is this 
what we want ?


happens-before across threads requires a volatile or lock, but I don't see 
> how the use of one is guaranteed by this logical ordering.
>

Volatiles and locks are means to an end. The end is memory visibility, and 
the happens-before partial ordering is what is of interest to us, 
application developers, to reason about this end. The happens-before rules 
have not changed since jsr-133 (source 
<http://download.java.net/java/jdk9/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility>)
 
:
* Each action in a thread happens-before every action in that thread that 
comes later in the program's order.
* An unlock (synchronized block or method exit) of a monitor happens-before 
every subsequent lock (synchronized block or method entry) of that same 
monitor. And because the happens-before relation is transitive, all actions 
of a thread prior to unlocking happen-before all actions subsequent to any 
thread locking that monitor.
* A write to a volatile field happens-before every subsequent read of that 
same field. Writes and reads of volatile fields have similar memory 
consistency effects as entering and exiting monitors, but do not entail 
mutual exclusion locking.
* A call to start on a thread happens-before any action in the started 
thread.
* All actions in a thread happen-before any other thread successfully 
returns from a join on that thread.

Here is an example of a multithreaded transducing context that doesn't use 
locks nor volatiles (inspired from the official documentation for agents 
<https://clojure.org/reference/agents>) :

(def xf (comp (partition-all 64) cat))

(defn ! [f & args] (apply f args) f)

(defn run [m n]
  (let [p (promise)
        f (reduce (fn [f _] (partial send (agent f) (xf !)))
                  #(when (zero? %) (deliver p nil)) (range m))]
    (doseq [i (reverse (range n))] (f i))
    (f)
    @p))

(run 1000 1000)


The unsynchronized ArrayList in partition-all will be accessed by multiple 
threads, and I can still be confident about visibility, because agents 
ensure a happens-before ordering between each message. This behaviour is 
actually delegated to the backing Executor, which may or may not use locks. 
Locks are really an implementation detail, what is important is 
happens-before guarantees.


Seems risky to depend on that. eduction creates an iterable for example - 
> it has no way of preventing somebody from creating the iterator on one 
> thread and consuming it on another. 
>

Iterators are unsynchronized and mutable, like many classes in the Java 
standard library. You know they're unsafe and need to treat them as such. 
This leads to a more general debate on mutability. Objects generally fall 
in 3 categories :
1. Immutable objects aka values. They're the default in Clojure and that's 
great because they can be exposed safely and they're so easy to reason 
about.
2. Thread-safe mutable objects. Includes core reference types, core.async 
channels. They're useful to model identity, they can be exposed safely but 
sparingly as they tend to complect the system in the large.
3. Unsafe mutable objects. Includes transients, Iterator, 
InputStream/OutputStream. They exist for performance or legacy reasons. 
They need extra care when used in multithreaded contexts and they should 
not be exposed as-is.

Clearly, reducing functions produced by stateful transducers are of the 
third type. You need to care if you're designing a transducing context, but 
most of the time you don't because :
- when you design a stateful transducer, you can write your code as if it 
were single-threaded because it will be run in a context that cares about 
concurrency for you
- once your ugly mutable state is encapsulated in a transducer, it's a 
value. Instead of handling a mutable process, you handle a recipe for 
making a mutable process, it's immutable and it's good.

Once again, I don't understand what's wrong with that.

Java, beeing mutable by default, has refrained to clutter its standard 
library with volatiles and locks. Instead, most classes are unsynchronized, 
optimized for single-threading. When synchronization is needed, we rely on 
a limited set of concurrency primitives. What's wrong with this approach ?



On Tuesday, April 11, 2017 at 5:24:38 PM UTC+2, Alex Miller wrote:
>
>
>>> Transducers should ensure stateful changes guarantee visibility. That 
>>> is: you should not make assumptions about external memory barriers.
>>
>>
>> How do you enforce no more than one thread at a time without setting a 
>> memory barrier ?
>>
>
> I could have one thread that invokes a transduce step on odd seconds and 
> another that invokes on even seconds. Or some external api call that tells 
> me to take the next step, which I do on a thread pulled from a pool. I'm 
> sure I could come up with others.
>  
>
>> For the JMM, no more than one thread at a time means exactly that return 
>> of step n will *happen-before* the call to step n+1.
>>
>
> happens-before across threads requires a volatile or lock, but I don't see 
> how the use of one is guaranteed by this logical ordering.
>
> This implies that what was visible to the thread performing step n will be 
>> visible to the thread performing the step n+1, including all memory writes 
>> performed during step n inside stateful transducers.
>>
>
> Only if there is a volatile or lock forcing that visibility.
>  
>
>> https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html 
>> <https://www.google.com/url?q=https%3A%2F%2Fwww.cs.umd.edu%2F~pugh%2Fjava%2FmemoryModel%2Fjsr-133-faq.html&sa=D&sntz=1&usg=AFQjCNFrKhTVAobPzLvc5FHLhtsUgQ5YTg>
>>  
>> Still no need for extra synchronization.
>>
>
> While this is a good reference, it's also 13 years old and the JMM has 
> been updated since then. A much better reference explaining the semantics 
> and constraints is:
>
> https://shipilev.net/blog/2014/jmm-pragmatics/
>
> In particular, even if there is a memory barrier, there are some 
> reorderings allowed if the transducer state is not volatile that may be 
> surprising. Making it volatile adds a critical edge in the total program 
> order.
>
> You're conflating the stateful values inside the transducer with the state 
>>> returned by and passed into a transducer. That's a linkage that does not 
>>> necessarily exist.
>>>
>>
>> What do you mean ? How could a function return a value without having 
>> executed its body ?
>>
>
> I'm saying that the logical ordering of steps is irrelevant wrt how a 
> multi-threaded program can be optimized/reordered under the JMM.
>
>

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