www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - shared: Has anyone used it without a lot of pain?

reply Atila Neves <atila.neves gmail.com> writes:
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
Apr 04
next sibling parent Jonathan M Davis via Digitalmars-d <digitalmars-d puremagic.com> writes:
On Tuesday, April 04, 2017 21:56:37 Atila Neves via Digitalmars-d wrote:
 I feel dirty if I write `__gshared`. I sneeze when I read it.
Well, it was designed with C global variables in mind, and it's pretty risky to use it for anything else, though you can get away with it if you're careful. However, I'm inclined to argue that __gshared really shouldn't ever be used on anything that's extern(D). The problem of course is that shared is a bit of a pain to use. Some of those pain points make sense even if they're annoying and some of them should probably be fixed.
 But everytime I try and use `shared` I get trouble for it.
Yeah. It works, but it is annoying to use.
 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?
Unfortunately, the idea seems to be that any user-defined objects that are marked as shared should have been designed to be shared and that a type would normally either always be shared or never shared. On some level, that makes sense, but it can also be really annoying, because sometimes, it really does make sense to use the same type in both shared and un-shared contexts. But the reality of the matter is that anything that isn't really basic is going to tend to have to either always be shared or never shared. The destructor case does seem like it should be fixed though. - Jonathan M Davis
Apr 04
prev sibling next sibling parent Dukc <ajieskola gmail.com> writes:
On Tuesday, 4 April 2017 at 21:56:37 UTC, Atila Neves wrote:
 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?
template flag parameter for the struct for being shared or not? I may well be missing something. But in hindsight it could work. Of course, you would still have to type the shared flag explicitly when instantiating which is less than desirable.
Apr 05
prev sibling next sibling parent reply Andrea Fontana <nospam example.com> writes:
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.
 [...]
 Atila
https://p0nce.github.io/d-idioms/#The-truth-about-shared
Apr 05
parent reply Guillaume Piolat <first.last gmail.com> writes:
On Wednesday, 5 April 2017 at 13:18:26 UTC, Andrea Fontana wrote:
 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.
 [...]
 Atila
https://p0nce.github.io/d-idioms/#The-truth-about-shared
Do we have a missed opportunity with shared? My pet peeve with shared is the RoI. You were supposed to use it whenever something is "shared across threads". My problem with it is with the RoI of putting entire object graphs under that type constructor. For now it seems shared!T is about being a monadic hatch for the adventurous, one you can bring your own meaning for.
Apr 05
next sibling parent reply Kagamin <spam here.lot> writes:
On Wednesday, 5 April 2017 at 14:01:24 UTC, Guillaume Piolat 
wrote:
 My pet peeve with shared is the RoI.
Risk of infection?
 You were supposed to use it whenever something is "shared 
 across threads". My problem with it is with the RoI of putting 
 entire object graphs under that type constructor.
If you don't want to share data between threads and have an option to do so, sure do it; shared is for the case when you want to share data.
Apr 06
parent reply Suliman <evermind live.ru> writes:
On Thursday, 6 April 2017 at 09:00:33 UTC, Kagamin wrote:
 On Wednesday, 5 April 2017 at 14:01:24 UTC, Guillaume Piolat 
 wrote:
 My pet peeve with shared is the RoI.
Risk of infection?
Return of Investment :)
Apr 06
parent Shachar Shemesh <shachar weka.io> writes:
On 06/04/17 12:37, Suliman wrote:
 On Thursday, 6 April 2017 at 09:00:33 UTC, Kagamin wrote:
 On Wednesday, 5 April 2017 at 14:01:24 UTC, Guillaume Piolat wrote:
 My pet peeve with shared is the RoI.
Risk of infection?
Return of Investment :)
Actually, to be pedantic, it's "return *on* investment". Shachar
Apr 06
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
On Wednesday, 5 April 2017 at 14:01:24 UTC, Guillaume Piolat 
wrote:
 Do we have a missed opportunity with shared?
Yes we do. The #1 problem is that it lack a bridge to and from the "normal" thread local world. there is literally no way to use shared in a correct way, you always need to bypass part of the language ATM. The 3 main way data are shared go as follow : 1/ Producer/consumer. Thread 1 create some object, send it to thread 2 for processing. This is common in server applications for instance, where a thread will accept request and then dispatch it to worker threads. Right now, there are no way to transfers ownership from one thread to another, so you pretty much got to cast to shared, move data to the other thread and then cast back to not shared. (People who followed closely will notice that this is somewhat entangled with other problem D has such as nogc exception, see http://forum.dlang.org/post/ikzzvbtvwhqweqlzxytz forum.dlang.org ). 2/ Actually shared objects with temporal ownership via a mutex. Once again, this is about ownership. One can get a temporary ownership of some shared object which one can manipulate as if it was thread local for the duration a mutex is held. The current way to do this is to take the mutex and cast away shared. This is bad as it breaks all type system guarantees. To be done safely, this is needs to specify what is actually owned by the object that is protected by the mutex and what isn't. A phobos facility could, granted ownership was known, take an object, take the mutex and then allow to access it in a "scope" manner based on the lifetime of the mutex's lock. the accessed object can even be const in case of RWlock and it works beautifully because const is transitive. 3/ Actually shared object providing method which use atomics and alike to be thread safe. This use case is actually decently served by shared today, except the construction of the object in the first place.
Apr 06
parent Guillaume Piolat <first.last gmail.com> writes:
On Thursday, 6 April 2017 at 13:52:01 UTC, deadalnix wrote:
 Right now, there are no way to transfers ownership from one 
 thread to another, so you pretty much got to cast to shared, 
 move data to the other thread and then cast back to not shared. 
 (People who followed closely will notice that this is somewhat 
 entangled with other problem D has such as nogc exception, see 
 http://forum.dlang.org/post/ikzzvbtvwhqweqlzxytz forum.dlang.org ).
Heretic idea floating in the air: kill shared with a secret ritual, transmute it into "owned", keep the number of type constructor constant :) The paper you linked also solves the "static this" having special rights on immutable data IIRC.
Apr 06
prev sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
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
Apr 05