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

Reply via email to