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