Trying to get the most out of the current 'shared' system
deadalnix
deadalnix at gmail.com
Mon Nov 12 05:00:26 PST 2012
Le 12/11/2012 12:41, Sönke Ludwig a écrit :
> Am 11.11.2012 19:46, schrieb Alex Rønne Petersen:
>> Something needs to be done about shared. I don't know what, but the
>> current situation is -- and I'm really not exaggerating here --
>> laughable. I think we either need to just make it perfectly clear that
>> shared is for documentation purposes and nothing else, or, figure out an
>> alternative system to shared, because I don't see shared actually being
>> useful for real world work no matter what we do with it.
>>
>
> After reading Walter's comment, it suddenly seemed obvious that we are
> currently using 'shared' the wrong way. Shared is just not meant to be
> used on objects at all (or only in some special cases like
> synchronization primitives). I just experimented a bit with a statically
> checked library based solution and a nice way to use shared is to only
> use it for disabling access to non-shared members while its monitor is
> not locked. A ScopedLock proxy and a lock() function can be used for this:
>
> ---
> class MyClass {
> void method();
> }
>
> void main()
> {
> auto inst = new shared(MyClass);
> //inst.method(); // forbidden
>
> {
> ScopedLock!MyClass l = lock(inst);
> l.method(); // now allowed as long as 'l' is in scope
> }
>
> // can also be called like this:
> inst.lock().method();
> }
> ---
>
> ScopedLock is non-copyable and handles the dirty details of locking and
> casting away 'shared' when its safe to do so. No tagging of the class
> with 'synchronized' or 'shared' needs to be done and everything works
> nicely without casts.
>
> This comes with a restriction, though. Doing all this is only safe as
> long as the instance is known to not contain any unisolated aliasing*.
> So use would be restricted to types that contain only immutable or
> unique/isolated references.
>
> So I also implemented an Isolated!(T) type that is recognized by
> ScopedLock, as well as functions such as spawn(). The resulting usage
> can be seen in the example at the bottom.
>
> It doesn't provide all the flexibility that a built-in 'isolated' type
> would do, but the possible use cases at least look interesting. There
> are still some details to be worked out, such as writing a spawn()
> function that correctly moves Isolated!() parameters instead of copying
> or the forward reference error mentioned in the example.
>
> I'll now try and see if some of my earlier multi-threading designs fit
> into this system.
>
> ---
> import std.stdio;
> import std.typecons;
> import std.traits;
> import stdx.typecons;
>
> class SomeClass {
>
> }
>
> class Test {
> private {
> string m_test1 = "test 1";
> Isolated!SomeClass m_isolatedReference;
> // currently causes a size forward reference error:
> //Isolated!Test m_next;
> }
>
> this()
> {
> //m_next = ...;
> }
>
> void test1() const { writefln(m_test1); }
> void test2() const { writefln("test 2"); }
> }
>
> void main()
> {
> writefln("Shared locking");
> // create a shared instance of Test - no members will
> // be accessible
> auto t = new shared(Test);
> {
> // temporarily lock t to make all non-shared members
> // safely available
> // lock() words only for objects with no unisolated
> // aliasing.
> ScopedLock!Test l = lock(t);
> l.test1();
> l.test2();
> }
>
> // passing a shared object to a different thread works as usual
> writefln("Shared spawn");
> spawn(&myThreadFunc1, t);
>
> // create an isolated instance of Test
> // currently, Test may not contain unisolated aliasing, but
> // this requirement may get lifted,
> // as long as only pure methods are called
> Isolated!Test u = makeIsolated!Test();
>
> // move ownership to a different function and recover
> writefln("Moving unique");
> Isolated!Test v = myThreadFunc2(u.move());
>
> // moving to a different thread also works
> writefln("Moving unique spawn");
> spawn(&myThreadFunc2, v.move());
>
> // another possibility is to convert to immutable
> auto w = makeIsolated!Test();
> writefln("Convert to immutable spawn");
> spawn(&myThreadFunc3, w.freeze());
>
> // or just loose the isolation and act on the base type
> writefln("Convert to mutable");
> auto x = makeIsolated!Test();
> Test xm = x.extract();
> xm.test1();
> xm.test2();
> }
>
> void myThreadFunc1(shared(Test) t)
> {
> // call non-shared method on shared object
> t.lock().test1();
> t.lock().test2();
> }
>
> Isolated!Test myThreadFunc2(Isolated!Test t)
> {
> // call methods as usual on an isolated object
> t.test1();
> t.test2();
> return t.move();
> }
>
> void myThreadFunc3(immutable(Test) t)
> {
> t.test1();
> t.test2();
> }
>
>
> // fake spawn function just to test the type constraints
> void spawn(R, ARGS...)(R function(ARGS) func, ARGS args)
> {
> foreach( i, T; ARGS )
> static assert(!hasUnisolatedAliasing!T ||
> !hasUnsharedAliasing!T,
> "Parameter "~to!string(i)~" of type"
> ~T.stringof~" has unshared or unisolated
> aliasing. Cannot safely be passed to a
> different thread.");
>
> // TODO: do this in a different thread...
> // TODO: don't cheat with the 1-parameter move detection
> static if(__traits(compiles, func(args[0])) ) func(args);
> else func(args[0].move());
> }
> ---
>
>
> * shared aliasing would also be OK, but this is not yet handled by the
> implementation.
With some kind of ownership in the type system, it can me made automagic
that shared is casted away on synchronized object.
More information about the Digitalmars-d
mailing list