How to use containers in lock based concurrency

Jonathan M Davis newsgroup.d at jmdavisprog.com
Fri Nov 3 22:24:58 UTC 2017


On Friday, November 03, 2017 21:23:02 Nathan S. via Digitalmars-d-learn 
wrote:
> Is this advice from 2015 outdated? I found it while I was
> wrestling with shared data structures, and after reading I
> stopped doing that.
>
> https://p0nce.github.io/d-idioms/#The-truth-about-shared
>
> >The truth about shared
> >It's unclear when and how shared will be implemented.
> >Virtually noone use shared currently. You are better off
> >ignoring it at this moment.

That advice was wrong when it was written. Plenty of folks don't understand
how to use shared properly, and there are lots of folks who cop out and use
__gshared instead, but shared _does_ work, even if it's annoying to use, and
__gshared is only intended for C globals. Using it on D objects is just
begging for problems, because the type system will treat them as
thread-local even though they're not. So, using __gshared can lead to
subtle, hard-to-find bugs that shared code won't have.

One of the problems with shared has been that it hasn't been used where it
should be (e.g. core.sync.mutex didn't use it, but I think that that's been
fixed now), making it harder to write shared code than it should be.
However, a lot of it just comes down to the fact that everyone balks at the
fact that shared requires casting to use properly. You rarely simply call
functions on shared objects, because if you did, there would be race
conditions. Rather, the way that shared should normally be used is
essentially:

shared(MyObject) mySharedObject;
// ...

synchronized(mutexForMySharedObject)
{
    auto myObj = cast(MyObject)mySharedObject;
    // do stuff with myObj
    // ...

    // make sure that at this point, there are no thread-local references
    // to mySharedObject which have escaped.
}

// And here, after the lock is released, we should just have the shared
// reference again.

It would be nice if we had a way to use shared that didn't require explicit
casting like that, but that code is essentially would you would do in a C++
program that was correctly dealing with objects shared across threads except
that the C++ program wouldn't have to cast away shared, because it puts
everything in shared memory whether it's actually used on multiple threads
or not, and its type system doesn't help you segregate code that operates on
objects that are used across threads. The annoyances with D's shared come
from it preventing you from doing stuff that actually isn't thread-safe and
requiring you to go to extra effort to then use the objects. What we end up
with is a bit like @safe and @trusted in that most of the code does not use
shared objects and has any shared objects that it does have explicitly
marked that way, and then there are small sections of code where you do the
risky thing that requires scrutiny where you cast away shared and operate on
it as if it were thread-local - of course, with a lock to ensure that it's
actually safe to operate on the object as thread-local, but all of that
requires that the programmer manually verify it rather than the compiler
being able to guarantee it for you like it can be preventing you from doing
stuff with shared objects that isn't thread-safe.

In principle, synchronized classes as described in TDPL are supposed to at
least partially solve the casting problem, because they would guarantee that
the objects don't escape from the synchronized class, allowing the compiler
to safely remove the outer layer of shared on member variables so that you
get that automatic cast - but only for the outer layer of shared (since they
can't guarantee that no other references to the data exist beyond the outer
layer). So, synchronized classes would only partially solve the problem. But
regardless, we don't actually have synchronized classes at the moment - just
synchronized functions - so we don't get even that much right now.

There is occasionally talk about overhauling shared in order to improve it,
but I think that most of that has to do with better defining spec-wise
what's going on with the memory model. We may very well be able improve some
of how shared is used as well, but fundamentally, it's very difficult to
guarantee that no other references could be mucking around with an object in
a given piece of code such that the compiler can automatically cast away
shared for you. As with @trusted, it requires the programmer to do the right
thing and verify that they're doing the right thing.

- Jonathan M Davis



More information about the Digitalmars-d-learn mailing list