On Thursday, 7 June 2012 at 23:51:27 UTC, Steven Schveighoffer
wrote:
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
You're forgetting about Global data.
I think rather the head shared should be striped as this fits
better with how D treats meaningless specifiers. And trying to
put structs that contain shared data on the stack should be
illegal.
shared int global_shared_int; // type is shared(int);
struct struct_with_shared_data
{
shared int shared_data_in_a_struct; // type is shared(int)
}
int main()
{
shared int not_really_shared; // type is int
shared int* unshared_ptr_to_shared_int; // type is shared(int)*;
struct_with_shared_data foo; // illegal, shared data can't
stored on the stack
// nor can the sharedness be striped.
}