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