[dmd-concurrency] synchronized, shared, and regular methods inside the same class
Andrei Alexandrescu
andrei at erdani.com
Mon Jan 4 17:00:21 PST 2010
Sean Kelly wrote:
> On Jan 4, 2010, at 4:12 PM, Andrei Alexandrescu wrote:
>
>> Sean Kelly wrote:
>>>>> So if I have:
>>>>> class A
>>>>> {
>>>>> void fn() shared { x = 5; }
>>>>> int x;
>>>>> }
>>>>> Is this legal? If the type of the object doesn't change then I'd guess that I won't be allowed to access non-shared fields inside a shared function?
>>>> Shared automatically propagates to fields, so typeof((new shared(A)).x) is shared int. Of course that's not the case right now; the typeof expression doesn't even compile :o).
>>> Hm... but what if fn() were synchronized instead of shared? Making x shared in that instance seems wasteful. I had thought that perhaps a shared function would simply only be allowed to access shared variables, and possibly call synchronized functions:
>>> class A {
>>> void fnA() shared { x = 5; } // ok, x is shared
>>> void fnB() shared { y = 5; } // not ok, y is not shared
>>> void fnC() synchronized { y = 5; } // ok, non-shared ops are ok if synchronized
>>> shared int x;
>>> int y;
>>> }
>> Aha! You've just discovered the tail-shared exemption: inside a synchronized method, direct fields can be accessed without barriers (neither implicit or explicit) although technically their type is still shared. Fortunately the compiler has all the information it needs to elide those barriers.
>
> Oh, I see. I can't decide if it's weird that this optimization means that the sharedRead() and sharedWrite() functions wouldn't be necessary, but I'm leaning towards saying that it's actually a good thing since the changed behavior is obvious.
Same here. All - please advise.
> The only catch with the approach above (and you've mentioned this before) is:
>
> class A {
> void fnA() shared { x = 5; }
> void fnB() synchronized { x = 6; }
> int x;
> }
>
> I had thought that explicitly labeling variables as shared would sidestep this by requiring ops on the vars to always be atomic. An alternative (as you've said before) would be to not allow shared and synchronized methods to both be used in a class, but it's pretty common that I'll want to do something like this:
>
> class A {
> void fnA() synchronized { sharedWrite( flag, true ); }
> void fnB() shared { return sharedRead( flag ); }
> shared flag;
> }
>
> Maybe my explicitly declaring flag as shared somehow provides an exemption? Or did you have another idea for a way around this?
Hmmm... if a field is shared in a non-shared object, it means you're not
using object's own lock to control access to that field. (You can e.g.
assume that other threads have the address of that field.) So that
field, inside a synchronized method, will not benefit of the tail-shared
exemption and will be subjected to all limitations specific to e.g. a
shared global bool.
Andrei
More information about the dmd-concurrency
mailing list