On 06/11/12 22:21, Steven Schveighoffer wrote:
>>> 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.

Yes; the suggestion was to also allow synchronized /expressions/, in addition
to statements. 


>> 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.

There's a precedent, mixin expressions.

However, there's no need to invent new constructs, as this already works:

    {
       auto unshared_p = p.locked;
       auto v1 = unshared_p.i;
       auto v2 = unshared_p.p;
       // etc
    }

and does not require compiler or language changes.

I'm using this idiom with mutexes and semaphores; the 'locked' implementation
is *extremely* fragile, it's very easy to confuse the compiler, which then
spits out nonsensical error messages and refuses to cooperate. But the above
should already be possible, only the return type could be problematic; keeping
'p' opaque would be best. I'll play with this when I find some time.

But 'synchronized' and 'shared' are really two different things, I probably
shouldn't have used your original example as a base, as it only added to the
confusion, sorry.

'synchronized' allows you to implement critical sections.
'shared' is just a way to mark some data as needing special treatment.

If all accesses to an object are protected by 'synchronized', either
explicitly or implicitly (by using a struct or class marked as
synchronized) then you don't need to mark the data as 'shared' at all.
It would be pointless - the thread that owns the lock also owns the
data.

'shared' is what lets you implement the locking primitives used by
synchronized and various lock-free schemes. (right now 'shared' alone
isn't powerful enough, yes)

You can use one or the other, sometimes even both, but they are not
directly tied to each other. So there's no need for 'synchronized'
to unshare anything, at least not in the simple mutex case.
Accessing objects both with and without holding a lock is extremely
rare.


> 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.     

    void f(scope S*);
    ...
    {
       auto locked_p = p.locked;
       f(locked_p.s);
    }

Requiring the signature to be 'void f(locked S*);' would not be a good
idea; this must continue to work and introducing another type would
exclude all code not specifically written with it in mind, like
practically all libraries.


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

Yes, but it needs another solution. 'scope' might be enough, but right
now we'd have to trust the programmer completely...

(It's about not leaking refs to *inside* the locked object, not just
'p' (or 'locked_p') itself)

artur

Reply via email to