<div dir="ltr"><div dir="ltr">On Tue, Aug 4, 2020 at 11:55 PM Sebastiaan Koppe via Digitalmars-d <<a href="mailto:digitalmars-d@puremagic.com">digitalmars-d@puremagic.com</a>> wrote:<br></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On Tuesday, 4 August 2020 at 12:52:01 UTC, Manu wrote:<br>
> On Tue, Aug 4, 2020 at 5:40 PM Sebastiaan Koppe via <br>
> Digitalmars-d < <a href="mailto:digitalmars-d@puremagic.com" target="_blank">digitalmars-d@puremagic.com</a>> wrote:<br>
>> There is just one thing about shared I don't understand. If I <br>
>> design my object such that the non-shared methods are to be <br>
>> used thread-local and the shared methods from any thread, it <br>
>> follows that I should be able to call said shared methods from <br>
>> both a shared and non-shared instance of that object.<br>
><br>
> Yes, this is a thing I talked at great length 1-2 years ago.<br>
> If you take shared to mean "is thread-safe", then my idea was <br>
> that<br>
> not-shared -> shared implicit conversion should be possible.<br>
> What I often do is this:<br>
><br>
> struct Thing<br>
> {<br>
>   ref shared(Thing) implSharedCast() { return <br>
> *cast(shared)&this; }<br>
>   alias implSharedCast this;<br>
> }<br>
><br>
> If that were an implicit conversion, that implies a slight <br>
> change of<br>
> meaning of shared (to one that I consider immensely more <br>
> useful), but it's<br>
> more challenging for the compiler to prove with confidence, and <br>
> there's a<br>
> lot of resistance to this change.<br>
<br>
What exactly does the compiler need to prove? The restrictions <br>
are all in place, you can only call shared methods on a shared <br>
object, and you can only access shared members in a shared method.<br></blockquote><div><br></div><div>Marking the function shared (and therefore the data accessible) doesn't magically make the function body threadsafe; what it does it makes the function potentially racy, and subject to VERY careful implementation.<br></div><div>Sadly, I'm not aware of any CS research that can help you write atomic/threadsafe functions with any degree of proof in an environment like this (ie, without isolation and/or things like copy-on-write, etc).</div><div></div><div><br></div><div>Calling shared methods from shared methods isn't safe either. Each call may be threadsafe atomically, but you can't author a leaf function without just as much care (or more) than the lower-level ones. If the function is more than 1-line, and carries some state across a few statements that call lower-level shared methods, then you need to be confident about the atomic state guarantees (or not! which is more likely) of the dependency API's, such that you don't create a state race between calls.</div><div><br></div><div>Needless to say, it's still tricky, and there's really nothing the language can help you do... other than provide a strong mechanism to lock it off from normal code.</div><div>As long as shared methods are few, and very well defined, then it is possible to implement very interesting and useful machines with this scheme, and also MASSIVELY help with validation of your ecosystem, and I have done so... but it's not magic, it's just a lot better than C++.</div><div><br></div><div></div><div>The thing `shared` protects most against, is failure to correct multithreaded code when refactoring. I maintain a majorly-superscalar engine, and 90% of the race bugs we have had to spend heaps of time chasing down are the result of refactors or changes that had very peripheral contact with the multithreaded core; contact was narrow enough that we didn't notice then making changes, and there was nothing in the type system to alert us. For this reason alone, `shared` is worth its weight in gold.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
Hmm, well I guess if members themselves were also implicitly <br>
promoted to shared that could cause havoc.<br></blockquote><div><br></div><div></div><div>The catch though, is that if you allow implicit conversion from unshared->shared, then the rule changes from "shared methods must be threadsafe", to "

shared methods must be threadsafe, AND unshared methods must also be threadsafe in the event they are called in conjunction with any of the shared methods (just not with eachother)".<br></div><div></div><div></div><div>While I have plenty of small tools where the second rule is easy to implement, I can also think of many situations where even the first rule is hard to implement, and the second rule is virtually impossible.</div><div><br></div><div>Unless we learn better ways to handle this, I don't think implicit conversion that way can scale. For the time being, you can implement the implicit conversion for the tools that may support that using `alias this` like I showed above, and I think that's appropriate for the time being.</div><div><br></div><div>In general, I'd like to find ways we can enhance shared to be more than a marker; consider TSAN (ThreadSanitizer) in Clang; it instruments atomics with runtime tracking to detect races at runtime. I think it would be possible to instrument shared data/methods in a similar way in debug builds, and then we'd have meaningful compiler assistance.</div></div></div>