shared: Has anyone used it without a lot of pain?

via Digitalmars-d digitalmars-d at puremagic.com
Wed Apr 5 09:39:58 PDT 2017


On Tuesday, 4 April 2017 at 21:56:37 UTC, Atila Neves wrote:
> I feel dirty if I write `__gshared`. I sneeze when I read it. 
> But everytime I try and use `shared` I get trouble for it.
>
> TIL that if I want a struct to be both `shared` and not, 
> destructors are out of the way. Because while constructors are 
> easy because we can have more than one:
>
> struct Foo {
>     this(this T)() { }
> }
>
> auto f1 = const Foo();
> auto f2 = shared Foo();
>
> There can be only one destructor:
>
>
> struct Bar {
>     this(this T)() { }
>     ~this() {} // no shared, if there was the problem would 
> reverse
>     // ~this(this T)() {} isn't a thing
> }
>
> auto b1 = const Bar();
> // Error: non-shared method foo.Bar.~this is not callable using 
> a shared object
> // auto b2 = shared Bar(); //oops
>
> The reason why what I was trying to do isn't possible is 
> obvious in hindsight, but it's still annoying. So either code 
> duplication or mixins, huh?
>
> Atila

The error message pretty much tells you that multiple threads 
should not be allowed to call the destructor concurrently, i.e. 
you should somehow guarantee that by the end of the scope only 
one thread has access to the object, which is what should happen 
in most multi-threaded programs.

A workable, but non the less dirty way of sharing RAII objects 
would be something along the lines:

struct Widget
{
     this()  { /* ... */ }

     void doWork() scope shared { /* ... */ }

     ~this() { /* ... */ }
}

Owner thread A
{
   /* 0) Make a new widget w/ automatic
      storage (RAII). Note: calling
      non-shared constructor since
      construction is a one thread endeavor
      anyway and we need non-shared `this`
      to call the destructor.             */
   auto w = Widget();

   /* 1) share `w` with other threads
      and perform some useful work...     */

   // Other thread B
   (ref scope shared(Widget) w) @safe
   {
     ssw.doWork();
   }

   /* 2) Ensure that A is now the only
      thread with reference to `w`.       */

   /* 3) w.~this() called automatically.
      Safe, since thread A is only
      one with reference to w.            */
}

2) can be achieved only if you pass `scope` references to `w` in 
1), so that non of the other threads would be able to store a 
pointer to `w` in a variable with longer lifetime.
You also need to have a way of ensuring that the other threads 
have shorter lifetime than `w` (i.e. simple fork-join 
parallelism), or you need some sort of task infrastructure that 
allows you to block thread A until thread B finishes working on 
the task created in 1) and ensuring no references have escaped in 
thread B.

The other approach is to not use RAII at all, but instead to use 
an Arc (atomic reference counting) wrapper that @trusted-ly knows 
to cast the 'shared' qualifier off Widget when the ref count 
drops to zero in order to call the destructor.

// create a non-shared Widget and transfer
// the ownership to the Arc wrapper which
// makes the object `shared` with the world.
auto arcW = Arc!(shared W)(new W());

// auto w = new W();
// auto arcFromNonUniqueW = Arc!(shared W)(w); <- doesn't compile

void use1(ref shared(W) w) @safe;
// arcW.get().use1(); // Doesn't compile:
// Error: reference to local variable arcW assigned to non-scope 
parameter w calling arc_test.use1

void use2(ref scope shared(W) w) @safe;
arcW.get().use2(); // OK


More information about the Digitalmars-d mailing list