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