On Monday, 14 August 2017 at 03:59:48 UTC, Jonathan M Davis wrote:
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
Yeah, and this is what i'm doing now (more or less).
To be more precise, I don't even want to synchronize access to
the shared resource between the threads. I just want to move the
object from one thread to another.
Of course I could copy the local object, but my obcjecs has
indirect references to others, forming kind of tree.
I like the idea of channels in Go. I've tried to get something
similiar with send/receive.
In this case, object could be also immutable, because once there
are created (in the deserialization process) they will no be
modified. I just have to emit them into another task (to be
honest, I use fiber, so it's not even another thread, and they
will not be accessed in parallel).
But all this language protections makes the issue unexpectedly
complicated.
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
Thanks for the explanation! It would be good to have a
comprehensive article on this subject.
Arek