Does anyone understand how to use "shared" types with concurrency send/receive functions?

Jonathan M Davis via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Sun Aug 13 20:59:48 PDT 2017


On Sunday, August 13, 2017 16:40:03 crimaniak via Digitalmars-d-learn wrote:
> More of this, I think, you can't avoid __gshared for any complex
> work. Even mutexes from Phobos doesn't support shared, so I had
> to 'cowboy with __gshared' when implementing my site engine.

The way to handle shared is to protect the section of code that's using the
shared object with either a mutex or synchronized block, and then you cast
away shared from the object within that section and operate on it as
thread-local. When you're done, you make sure that you don't have any
thread-local references to the data, and you release the mutex or exit the
synchronized block. e.g. something like

shared T sharedObj = getSharedObj();

synchronized(mutex)
{
    T nonSharedObj = cast(T)sharedObject

    // do stuff...

    // make sure that no references to nonSharedObj have escaped
}

// now, there's just the shared version of the object

And no, this isn't ideal, but the only semi-decent solution that's been
proposed that safely casts away shared for you is synchronized classes,
which Andrei describes in TDPL but have never been implemented. And because
they can only safely strip off the outermost layer of shared, they're of
questionable usefulness anyway. Ultimately, even with synchronized classes,
in many situations, the programmer is going to have to carefully cast away
shared to operate on the object within a protected context.

Now, the fact that the mutex objects don't handle shared correctly is
another issue entirely. Having to cast away shared from mutexes is dumb,
because you're obviously not going to be protecting them with a mutex, and
their operations have to be atomic anyway for them to do what they do. So,
that definitely needs to be fixed. However, I believe that it _has_ been
fixed in master, and it might have made it into a release now, but I'm not
sure. So, core.sync.mutex.Mutex _should_ now be useable as shared like it
should be.

In general though, the idea is that you simply don't operate on shared
objects except via atomic operations. Otherwise, you risk concurrency
problems. And really, this is the same as what you'd do in C/C++, except
that in C/C++, it doesn't catch you when you operate on an object that's
shared across threads with non-atomic operations (because the object isn't
explicitly typed as shared), and you don't have to cast away shared to do
non-atomic operations. So, having to cast away shared is the price of
getting the protection against accidentally using non-atomic operations on a
shared object as well as the price we pay to be able to have the type system
distinguish between shared and thread-local objects so that it's able to
optimize based on the knowledge that an object is thread-local. Ultimately
though, you're doing the same thing that you'd do in C++ if you're handling
concurrency safely. You just have to explicitly mark stuff as shared and
carefully cast away shared in certain, protected contexts.

Using __gshared in extern(D) code is just asking for it, because then you
have an object that the compiler thinks is thread-local but isn't, and you
risk subtle and nasty bugs as a result. __gshared is only intended for
binding to extern(C), global variables. To an extent, you can get away with
using it with extern(D) variables, but that's not its intended purpose, and
you risk running afoul of the compiler and what it chooses to do based on
the assumption that the object is thread-local.

- Jonathan M Davis



More information about the Digitalmars-d-learn mailing list