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