First draft of translation document
Reinier Zwitserloot
reinier at zwitserloot.com
Thu May 20 08:12:43 PDT 2010
Reasoning about this for the VM gets a lot easier if the VM knows for sure
that the closure will not escape from its current stack frame. In other
words, in code like this:
list.filter(#(Person p) (p.age() < 20));
assuming for a moment that filter is 'eager', that closure won't escape. It
doesn't end up in a field somewhere, it doesn't run in another thread. If
the GC runs immediately following the termination of this method (and the
closure is represented as some sort of object), it would be eligible for GC.
If this is indeed the case there is no need to mark any shared variables as
volatile. It may be possible to allocate on the stack whatever frame objects
are required. Perhaps if in future "long returns" are added (the ability to
have break / continue / return retain their meaning as if the code was
written in the scope from the method itself, not from the closure), any such
activities cease to make sense if the closure escapes its stack context, but
if the VM knows for sure the closure won't escape it may be able to optimize
the way those constructs work (better than the BGGA exception mechanism).
But not just the VM benefits, so does the programmer. By knowing that a
certain closure does not escape, the programmer has a much easier time
reasoning about the effects of sharing a non-final variable between closure
and hosting block.
BGGA had something somewhat similar in the 'safe' vs. 'unsafe' closures
concept.
Would it be possible to support some sort of marker on a method parameter
that means: "I will ensure this parameter will not escape", which is
enforced by the compiler and verified by the verifier? This way the
aforementioned "filter" method could include this marker on the 1 closure
parameter it takes, and from there the JVM itself and javac during
compilation of "list.filter(#(Person p) (p.age() < 20));" can react knowing
that this closure won't escape. For example, the compiler may silently
"share" mutable variables knowing the pragmatic different is moot, at least
for the programmer, but if this is NOT a guaranteed-no-escape kind of
closure, it could require an explicit "shared" keyword.
NB: "Escaping" is defined as: Assigning the closure to anything, sharing
with an anonymous inner class or method local class (or potentially escaping
closure), using the variable as a parameter, unless used as a parameter to a
method where that parameter is also marked 'no escape guaranteed'.
--Reinier Zwitserloot
On Wed, May 19, 2010 at 7:08 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
> >>> void f() {
> >>> shared String s = "hello";
> >>> Utils.perhapsRunInAnotherThread(#() {
> >>> System.out.println(s); // may or may not prints null
> >>> });
> >>> }
> >>>
> >> The initialization of the variable s "happens before" the thread is
> >> even created, so there is no race condition. This program always
> >> prints "hello". This is no different than if s had been a field.
> >>
> >
> > I am not a concurrency guru but I think you are wrong.
> > The field s of the Frame can be printed by another thread before it is
> > initialization.
> > The local variable s will be correctly initialized but not the
> > corresponding field in Frame.
>
> Since 'shared' does not yet exist in the language, we don't know what its
> semantics are. Therefore Neal cannot be wrong! (He also cannot be right.)
>
> But lets imagine there was such a language feature, and it had a
> specification. Maybe shared variables are implicitly volatile; maybe
> shared
> variables can be declared explicitly volatile in addition to shared.
>
> Reasoning about either case is not so easy. We probably have to assume
> that
> Utils is written such that the closure is not passed from thread to thread
> via
> a data race; that there has to be a happens-before between some point
> inside
> perhapsRunInAnotherThread (call it X) and the execution of the closure body
> in
> the other thread (call it Y).
>
> With this assumption, in both cases there is no data race between the
> initialization and the use, since the initialization happens-before X (and
> by
> transitivity, it happens-before the use at Y).
>
> If the assumption fails to hold, making s volatile will generate a
> happens-before ordering even if the closure is passed via a data race, but
> calls into question other aspects of the closure's internal state (such as
> the
> frame pointer.)
>
> The same argument applies in reverse:
>
> void f() {
> shared String s;
> Utils.perhapsRunInAnotherThread(#() {
> s = "blarg";
> });
> System.out.println(s);
> }
>
> Where we now need a happens-before between the write in the other thread
> and
> the read in the calling thread. Note that this is totally outside the
> semantics of closures; it is about the behavior of Utils. So now we find
> ourselves reasoning about the JMM characteristics of the dispatch logic in
> Utils, which is of course opaque and nearly always underspecified.
>
> Note also that the JSR-292 EG may want to make stronger representations
> about
> whether the various MH combinators behave effectively like setting final
> fields in a constructor, or whether the internal state in the combinator
> nodes
> are ordinary nonvolatile mutable state.
>
>
>
More information about the lambda-dev
mailing list