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