Possible to pass a member function to spawn?

Timon Gehr timon.gehr at gmx.ch
Wed Feb 8 09:24:20 PST 2012


On 02/08/2012 12:33 PM, Manu wrote:
> On 8 February 2012 01:53, Timon Gehr <timon.gehr at gmx.ch
> <mailto:timon.gehr at gmx.ch>> wrote:
>
>     On 02/08/2012 12:09 AM, Manu wrote:
>
>         On 8 February 2012 00:33, Sean Kelly <sean at invisibleduck.org
>         <mailto:sean at invisibleduck.org>
>         <mailto:sean at invisibleduck.org
>         <mailto:sean at invisibleduck.org>__>> wrote:
>
>             On Feb 6, 2012, at 1:38 PM, Oliver Puerto wrote:
>
>          > Hello,
>          >
>          > I'm very new to D. Just started reading "The D programming
>             language". I should read it from beginning to end before posting
>             questions here. I know ... But I'm just too impatient. The issue
>             seems not to be that simple, nevertheless. The code below
>         compiles
>             with Visual Studio.
>          >
>          > I want to have something like my actor class that I can start
>             running in it's own thread like in Scala or other languages that
>             support actors. So at best, I would like to do something
>         like this:
>          >
>          >    MyActor myActor = new MyActor();
>          >    auto tid = spawn(&start, &myActor.run());
>
>             This should work:
>
>             void runActor(shared MyActor a) { (cast(MyActor)a)).run(); }
>             MyActor myActor = new MyActor();
>             auto tid = spawn(cast(shared MyActor) myActor, &runActor);
>
>
>         See, my conclusion is, whenever using this API, you inevitably
>         have dog
>         ugly code.
>
>
>     If it is combined with OO.
>
>
> The OO changes nothing, the casting is what I'm getting at.

OO makes heavy use of reference types. Casting to shared is only 
necessary for data that contains references.


>
>         That code is barely readable through the casts... I can only
>         draw this up to faulty API design.
>         I understand the premise of 'shared'-ness that the API is trying to
>         assert/guarantee, but the concept is basically broken in the
>         language.
>
>
>     The concept is not broken at all. There are just too few type system
>     features to conveniently support the concept.
>
>
> ... I think you contradicted yourself one sentence after the other :)

The first sentence talks about the concept, the second sentence talks 
about its implementation. It is important to keep the two separate.

>
> You can't use this API at all with out these blind casts, which is,
>
>         basically, a hack, and I am yet to see an example of using this API
>         'properly'.
>
>
>     Passing value type and/or immutable messages works well.
>
>
> I guess so, but I think this would be relatively rare. Many immutable
> things may just be addressed directly/globally.
> Maybe it's just my experience of threading, but I basically never spawn
> a thread where I don't intend to pass some sort of 'workload' to it... I
> shouldn't have to jump through hoops to achieve that basic task.
>
>         The casts are totally self defeating.
>
>
>     They indicate a potentially unsafe operation.
>
>
> Sure, but what's the point of 'indicating' such a thing, when the ONLY
> way to deal with it, is to add ugly blind casts? You are forced to
> ignore the warning and just cast the 'problem' away.
>

You use the cast to work around type system limitations, you are not 
allowed to break the type system. I think it is nice that the absence of 
the shared keyword means that nothing is shared between threads.

>             std.concurrency really should allow unique references to a
>             non-shared type to be passed as well, using something similar to
>             assumeUnique.
>
>
>         Something like that should exist in the language (... or shared
>         should
>         just not be broken).
>
>
>     How would you improve usability of the shared qualifier without some
>     kind of ownership type system?
>
>
> I've thought about that... but I've got nothing. The shared concept
> seems basically broken to me. It's nothing more than a
> self-documentation keyword, with the added bonus that it breaks your
> code and forces casts everywhere you touch it.
> It creates a boundary between 2 worlds of objects. Nothing shared can be
> used in an un-shared environment (ie. all your code), and nothing
> unshared can be passed to a shared environment... and there's no
> implicit(/safe) transfer between the 2 worlds.
> The ONLY way to interact is explicit cast, which doesn't guarantee any
> safety, you just have to type it wherever the compile errors pop up. So
> what's the point?

Do you see the bug in the following Java code?

class Code {
     Code other;
     void foo(int x) {
         if(other != null && x>0) {
             other.foo(x-1);
         }else other = null;
     }
}

Hint: There is no bug if the code is interpreted as D code instead.


> Without a way for the language to assert what
> transfers between worlds are safe or not, the whole premise is self
> defeating.
>

Not necessarily.

> Passing args by value seems to be the only solution, but that doesn't
> work in an OO environment.
>
> Do you have any ideas how to make it work?

http://xkcd.com/356/


I think we'd indeed need ownership / an 'unique' type qualifier. This 
could also be helpful:
http://d.puremagic.com/issues/show_bug.cgi?id=7316

I think type system controlled aliasing is very important to make it 
work, so another important step would be to enforce the 'scope' storage 
class through flow-analysis.

As far as the 'unique' type qualifier goes, we need to find a suitable 
design:


void main(){
     auto x = new C; // if constructor is pure or scope, x can be unique
     pragma(msg, typeof(x)); // ?
     auto y = x; // ?
     pragma(msg, typeof(x)," ",typeof(y)); // ?
     x.foo(); // ?
     spawn(&bar, x) // ?
}

So maybe it should look more like this:

void main(){
     auto x = new C; // pure/scope constructor
     static assert(typeof(x) == C);
     auto y = x;
     y.foo(); // foo is pure or scope
     spawn(&bar, unique x); // fine
}

This would add unique as an unary operator to perform a safe conversion 
to unique. Similar operators could be added for 
immutable/shared/const/inout. Such an approach would be 
backwards-compatible.

This solution uses flow-analysis to make sure that no alias to x is 
alive after the safe conversion to unique.
Because of the guarantees that unshared gives, the flow analysis can 
even include unshared fields.

We could even do:

void spawn(F,T...)(F fun, T args) if(!mayHaveMutableNonUniqueAliasing!T) 
{ ... }
void spawn(F,T...)(F fun, unique T args) 
if(mayHaveMutableNonUniqueAliasing!T) { spawn(fun, args); }

Then the example would look like:

void main(){
     auto x = new C; // pure or scope
     auto y = x;
     y.foo(); // pure or scope
     spawn(&bar, x); // fine!
}

x converts to unique if flow analysis can prove no alias to it is alive 
after the conversion. The first spawn overload does not match, therefore 
the second one is chosen. x is implicitly converted to unique and then 
the other overload is used to finish up.


Open issue:

void main(){
     unique x = new C;
     pragma(msg, typeof(x.z)); // ???
     auto y = x.z; // error?
     immutable z = x;
}

x.z is not necessarily unique, but assigning it to y breaks the 
guarantees of the unique qualifier of x.

Ideally there would also be a way to say 'this is the only mutable 
reference, it is implicitly convertible to immutable, but not to 
shared'. const(unique(T)) could be used (because const(unique) does not 
have any other sane meaning), but it would probably be a misnomer.


TDPL states that 'shared' enforces sequential consistency. This is not 
implemented yet, and if it won't be, 'scope shared' could be used to 
link the shared and unshared worlds:

class C{
     void foo()scope shared{ ... }
}

void main(){
     auto a = new shared(C);
     auto b = new C;
     a.foo(); // fine
     b.foo(); // fine
}


> I get the feeling, as I say, shared is a nice idea, but it's basically
> broken, and the language doesn't have any mechanism to really support
> it. As is, it's nothing more than an info-keyword that makes your code
> ugly :/

I think we should fix it, because such information could turn out to be 
quite important on NUMA. Furthermore, it could be possible that on such 
architectures, the best way to do message passing is to serialize the 
whole object graph and re-build it in the other core's local memory 
space anyway.

>
>
>         Using a template like assumeUnique is no better
>         than the ugly cast. What does it offer over a cast?
>
>
>     Nothing. I prefer cast(immutable).
>
>
> I'd prefer to not have shared at all in it's current state. It adds zero
> value that I can see, and has clear negative side effects.

I see your point.


More information about the Digitalmars-d mailing list