On Thursday, 7 June 2012 at 23:51:27 UTC, 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
}

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


You're forgetting about Global data.
I think rather the head shared should be striped as this fits better with how D treats meaningless specifiers. And trying to put structs that contain shared data on the stack should be illegal.

shared int global_shared_int; // type is shared(int);

struct struct_with_shared_data
{
  shared int shared_data_in_a_struct; // type is shared(int)
}

int main()
{
  shared int not_really_shared; // type is int
  shared int* unshared_ptr_to_shared_int; // type is shared(int)*;

struct_with_shared_data foo; // illegal, shared data can't stored on the stack
                     // nor can the sharedness be striped.
}

Reply via email to