valid uses of shared

Artur Skawina art.08.09 at gmail.com
Thu Jun 7 17:58:13 PDT 2012


On 06/08/12 01:51, 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
> }

Note that the type of 'x' in

   shared struct S {
      int x;
   }

should probably be 'shared(int)'.
Which lets you safely take an address of an aggregates field.

And I'm not sure if marking a struct and class as shared would work
correctly right now, it's probably too easy to lose the 'shared' qualifier.


> shared class C  // legal, C is always a reference type
> {
>    shared int x; // illegal, but useless, since C is already shared
> }

Redundant, but should be accepted, just like 'static' is.


> 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.

That's too restrictive. It would overload 'shared' even more. If you
want that kind of synchronize magic to work, just allow:

   shared synchronized(optional_locking_primitive) struct S {
      ...
   }

And *now* 'x.y = 5' can do its magic, while '&x.y' can be disallowed.


   shared struct S {
      Atomic!int i;
   }
   shared(S)* p = ...
   p.i += 1;

should work, so accessing fields must remain possible.


> auto would have to strip shared:
> 
> auto myY = x.y; // typeof(myY) == int.

Hmm, i'm not sure about this, maybe it should be disallowed; it could
only strip 'shared' from the head anyway.

> This is definitely not a complete proposal.  But I wonder if this is the right direction?

I think it is.

artur


More information about the Digitalmars-d mailing list