valid uses of shared

Steven Schveighoffer schveiguy at yahoo.com
Mon Jun 11 13:21:04 PDT 2012


On Mon, 11 Jun 2012 15:23:56 -0400, Artur Skawina <art.08.09 at gmail.com>  
wrote:

> On 06/11/12 19:27, Steven Schveighoffer wrote:

>> Perhaps the correct way to implement shared semantics is to not allow  
>> access *whatsoever* (except taking the address of a shared piece of  
>> data), unless you:
>>
>> a) lock the block that contains it
>> b) use some library feature that uses casting-away of shared to  
>> accomplish the correct thing.  For example, atomicOp.
>
> Exactly; this is what I'm after the whole time. And I think it can be  
> done
> in most cases without casting away shared. For example by allowing the  
> safe
> conversions from/to shared of results of expression involving shared  
> data,
> but only under certain circumstances. Eg in methods with a shared 'this'.

Good, I'm glad we are starting to come together.

>> None of this can prevent deadlocks, but it does create a way to prevent  
>> deadlocks.
>>
>> If this was the case, stack data would be able to be marked shared, and  
>> you'd have to use option b (it would not be in a block).  Perhaps for  
>> simple data types, when memory barriers truly are enough, and a  
>> shared(int) is on the stack (and not part of a container), straight  
>> loads and stores would be allowed.
>
> Why? Consider the case of function that directly or indirectly launches  
> a few
> threads and gives them the address of some local shared object. If the  
> current
> thread also accesses this object, which has to be possible, then it must  
> obey
> the same rules.

I think this is possible for what I prescribed.  You need a special  
construct for locking and using shared data on the stack (for instance  
Lockable!S).

Another possible option is to consider the stack frame as the "container",  
and if it contains any shared data, put in a hidden mutex.

In order to do this correctly, we need a way to hook synchronized properly  
 from library code.

>> Now, would you agree that:
>>
>> auto v1 = synchronized p.i;
>>
>> might be a valid mechanism?  In other words, assuming p is lockable,  
>> synchronized p.i locks p, then reads i, then unlocks p, and the result  
>> type is unshared?
>
> I think I would prefer
>
>    auto v1 = synchronized(p).i;

This kind of makes synchronized a type constructor, which it is not.

> ie for the synchronized expression to lock the object, return an unshared
> reference, and the object be unlocked once this ref goes away. RLII. ;)
>
> Which would then also allow for
>
>    {
>       auto unshared_p = synchronized(p);
>       auto v1 = unshared_p.i;
>       auto v2 = unshared_p.p;
>       // etc
>    }

I think this can be done, but I would not want to use synchronized.  One  
of the main benefits of synchronized is it's a block attribute, not a type  
attribute.  So you can't actually abuse it.

The locked type I specify below might fit the bill.  But it would have to  
be hard-tied to the block.  In other words, we would have to make *very*  
certain it would not escape the block.  Kind of like inout.

>> Also, inside synchronized(p), p becomes tail-shared, meaning all data  
>> contained in p is unshared, all data referred to by p remains shared.
>>
>> In this case, we'd need a new type constructor (e.g. locked) to  
>> formalize the type.
>
> I should have read to the end i guess. :)
>
> You mean something like I described above, only done by mutating
> the type of 'p'? That might work too.

Right, any accesses to p *inside* the block "magically" become locked(S)  
instead of shared(S).  We have to make certain locked(S) instances cannot  
escape, and we already do something like this with inout -- just don't  
allow members or static variables to be typed as locked(T).

I like replacing the symbol because then it doesn't allow you access to  
the outer symbol (although you can get around this, it should be made  
difficult).  As long as the locks are reentrant, it shouldn't pose a large  
problem, but obviously you should try and avoid locking the same data over  
and over again.

One interesting thing: synchronized methods now would mark this as  
locked(typeof(this)) instead of typeof(this).  So you can *avoid* the  
locking and unlocking code while calling member functions, while  
preserving it for the first call.

This is important -- you don't want to escape a reference to the unlocked  
type somewhere.

-Steve


More information about the Digitalmars-d mailing list