Something needs to happen with shared, and soon.

Jonathan M Davis jmdavisProg at gmx.com
Thu Nov 15 05:00:27 PST 2012


On Thursday, November 15, 2012 14:32:47 Manu wrote:
> On 15 November 2012 13:38, Jonathan M Davis <jmdavisProg at gmx.com> wrote:

> I don't really see the difference, other than, as you say, the cast is
> explicit.
> Obviously the possibility for the situation you describe exists, it's
> equally possible with the cast, except this way, the usage pattern is made
> more convenient, the user has a convenient way to control the locks and
> most importantly, it would work with templates.
> That said, this sounds like another perfect application of 'scope'. Perhaps
> only scope parameters can receive a locked, shared thing... that would
> mechanically protect you against escape.

You could make casting away const implicit too, which would make some code 
easier, but it would be a disaster, because the programer wouldn't have a clue 
that it's happening in many cases, and the code would end up being very, very 
wrong. Implicitly casting away shared would put you in the same boat. _Maybe_ 
you could get away with it in very restricted circumstances where both pure 
and scope are being used, but then it becomes so restrictive that it's nearly 
useless anyway. And again, it would be hidden from the programmer, when this 
is something that _needs_ to be explicit. Having implicit locks happen on you 
could really screw with any code trying to do explicit locks, as would be 
needed anyway in all but the most basic cases.

> 2. It's often the case that you need to lock/unlock groups of stuff together
> > such that locking specific variables is of often of limited use and would
> > just
> > introduce pointless extra locks when dealing with multiple variables. It
> > would
> > also increase the risk of deadlocks, because you wouldn't have much - if
> > any -
> > control over what order locks were acquired in when dealing with multiple
> > shared variables.
> 
> Your fear is precisely the state we're in now, except it puts all the work
> on the user to create and use the synchronisation objects, and also to
> assert that things are locked when they are accessed.
> I'm just suggesting some reasonably simple change that would make the
> situation more usable and safer immediately, short of waiting for all these
> fantastic designs being discussed having time to simmer and manifest.

Except that with your suggestion, you're introducing potential deadlocks which 
are outside of the programmer's control, and you're introducing extra overhead 
with those locks (both in terms of memory and in terms of the runtime costs). 
Not to mention, it would probably cause all kinds of issues for something like 
shared int* to have a mutex with it, because then its size is completely 
different from int*. It also would cause even worse problems when that shared 
int* was cast to int* (aside from the size issues), because all of the locking 
that was happening for the shared int* was invisible. If you want automatic 
locks, then use synchronized classes. That's what they're for.

Honestly, I really don't buy into the idea that it makes sense for shared to 
magically make multi-threaded code work without the programmer worrying about 
locks. Making it so that it's well-defined as to what's atomic is great for 
code that has any chance of being lock-free, but it's still up to the 
programmer to understand when locks are and aren't needed and how to use them 
correctly. I don't think that it can possibly work for it to be automatic. 
It's far to easy to introduce deadlocks, and it would only work in the 
simplest of cases anyway, meaning that the programmer needs to understand and 
properly solve the issues anyway. And if the programmer has to understand it 
all to get it right, why bother adding the extra overhead and deadlock 
potential caused by automatically locking anything? D provides some great 
synchronization primitives. People should use them.

I think that the only things that share really needs to be solving are:

1. Indicating to the compiler via the type system that the object is not 
thread-local. This properly segregates shared and unshared code and allows the 
compiler to take advantage of thread locality for optimizations and avoid 
optimizations with shared code that screw up threading (e.g. double-checked 
locking won't work if the compiler does certain optimizations).

2. Making it explicit and well-defined as part of the language which operations 
can assumed to be atomic (even if it that set of operations is very small, 
having it be well-defined is valuable).

3. Ensuring sequential consistency so that it's possible to do lock-free code 
when atomic operations permit it and so that there are fewer weird issues due 
to undefined behavior.

- Jonathan M Davis


More information about the Digitalmars-d mailing list