valid uses of shared
Steven Schveighoffer
schveiguy at yahoo.com
Thu Jun 7 16:51:28 PDT 2012
I am having a quite interesting debate on pure and shared with Artur
Skawina in another thread, and I thought about how horrible a state shared
is in. It's not implemented as designed, and the design really leaves
more questions than it has answers. In addition, it has not real
connection with thread synchronization whatsoever, and Michel Fortin
suggested some improvements to synchronized that look really cool that
would involve shared.
So I thought about, what are the truly valid uses of shared? And then I
thought, more importantly, what are the *invalid* uses of shared?
Because I think one of the biggest confusing pieces of shared is, we have
no idea when I should use it, or how to use it. So far, the only benefit
I've seen from it is when you mark something as not shared, the things you
can assume about it.
I think a couple usages of shared make very little sense:
1. having a shared piece of data on the stack.
2. shared value types.
1 makes little sense because a stack is a wholly-owned subsidiary of a
thread. Its existence depends completely on the stack frame staying
around. If I share a piece of my stack with another thread, then I return
from that function, I have just sent a dangling pointer over to the other
thread.
2 makes little sense because when you pass around a value type, it's
inherently not shared, you are making a copy! What is the point of
passing a shared int to another thread? Might as well pass an int (this
is one of the sticking points I have with pure functions accepting or
dealing with shared data).
I have an idea that might fix *both* of these problems.
What if we disallowed declarations of shared type constructors on any
value type? So shared(int) x is an error, but shared(int)* x is not (and
actually shared(int *) x is also an error, because the pointer is passed
by value). However, the type shared(int) is valid, it just can't be used
to declare anything.
The only types that could be shared, would be:
ref shared T => local reference to shared data
shared(T) * => local pointer to shared data
shared(C) => local reference to shared class
And that's it.
The following would be illegal:
struct X
{
shared int x; // illegal
shared(int)* y; // legal
shared(X) *next; // legal
}
shared class C // legal, C is always a reference type
{
shared int x; // illegal, but useless, since C is already shared
}
If you notice, I never allow shared values to be stored on the stack, they
are always going to be stored on the heap. We can use this to our
advantage -- using special allocators that are specific to shared data, we
can ensure the synchronization tools necessary to protect this data gets
allocated on the heap along side it. I'm not sure exactly how this could
work, but I was thinking, instead of allocating a monitor based on the
*type* (i.e. a class), you allocate it based on whether it's *shared* or
not. Since I can never create a shared struct X on the stack, it must be
in the heap, so...
struct X
{
int y;
}
shared(X) *x = new shared(X);
synchronized(x) // scope-locks hidden allocated monitor object
{
x.y = 5;
}
x.y = 5; // should we disallow this, or maybe even auto-lock x?
Hm... another idea -- you can't extract any piece of an aggregate. That
is, it would be illegal to do:
shared(int)* myYptr = &x.y;
because that would work around the synchronization.
auto would have to strip shared:
auto myY = x.y; // typeof(myY) == int.
This is definitely not a complete proposal. But I wonder if this is the
right direction?
-Steve
More information about the Digitalmars-d
mailing list