shared - i need it to be useful

Steven Schveighoffer schveiguy at gmail.com
Tue Oct 16 13:25:16 UTC 2018


On 10/15/18 2:46 PM, Manu wrote:
> Okay, so I've been thinking on this for a while... I think I have a
> pretty good feel for how shared is meant to be.
> 
> 1. shared should behave exactly like const, except in addition to
> inhibiting write access, it also inhibits read access.
> 
> I think this is the foundation for a useful definition for shared, and
> it's REALLY easy to understand and explain.
> 
> Current situation where you can arbitrarily access shared members
> undermines any value it has. Shared must assure you don't access
> members unsafely, and the only way to do that with respect to data
> members, is to inhibit access completely.
> I think shared is just const without read access.
> 
> Assuming this world... how do you use shared?
> 
> 1. traditional; assert that the object become thread-local by
> acquiring a lock, cast shared away
> 2. object may have shared methods; such methods CAN be called on
> shared instances. such methods may internally implement
> synchronisation to perform their function. perhaps methods of a
> lock-free queue structure for instance, or operator overloads on
> `Atomic!int`, etc.
> 
> In practise, there is no functional change in usage from the current
> implementation, except we disallow unsafe accesses (which will make
> the thing useful).
> 
>>From there, it opens up another critical opportunity; T* -> shared(T)*
> promotion.
> Const would be useless without T* -> const(T)* promotion. Shared
> suffers a similar problem.
> If you write a lock-free queue for instance, and all the methods are
> `shared` (ie, threadsafe), then under the current rules, you can't
> interact with the object when it's not shared, and that's fairly
> useless.
> 
> Assuming the rules above: "can't read or write to members", and the
> understanding that `shared` methods are expected to have threadsafe
> implementations (because that's the whole point), what are the risks
> from allowing T* -> shared(T)* conversion?
> 
> All the risks that I think have been identified previously assume that
> you can arbitrarily modify the data. That's insanity... assume we fix
> that... I think the promotion actually becomes safe now...?
> 
> Destroy...
> 

This is a step in the right direction. But there is still one problem -- 
shared is inherently transitive.

So casting away shared is super-dangerous, even if you lock the shared 
data, because any of the subreferences will become unshared and 
read/writable.

For instance:

struct S
{
    int x;
    int *y;
}

shared int z;

auto s1 = shared(S)(1, &z);

auto s2 = shared(S)(2, &z);

S* s1locked = s1.lock;

Now I have access to z via s1locked as an unshared int, and I never 
locked z. Potentially one could do the same thing via s2, and now there 
are 2 mutable references, potentially in 2 threads.

All of this, of course, is manual. So technically we could manually 
implement it properly inside S. But this means shared doesn't help us much.

We really need on top of shared, a way to specify something is 
tail-shared. That is, all the data in S is unshared, but anything it 
points to is still shared. That at least helps the person implementing 
the manual locking from doing stupid things himself.

-Steve


More information about the Digitalmars-d mailing list