Reordering definitely matters:

StepA: write to x
StepB: read from x

StepB: read from x
StepA: write to x

On Wednesday, April 12, 2017 at 7:15:09 AM UTC-5, Léo Noel wrote:
>
> 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