[dmd-concurrency] synchronized, shared, and regular methods inside the same class
Jason House
jason.james.house at gmail.com
Mon Jan 4 17:16:46 PST 2010
On Jan 4, 2010, at 8:00 PM, Andrei Alexandrescu <andrei at erdani.com>
wrote:
> 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.
I like this style as long as the compiler isn't completely brain dead
and incorrectly force sme to put sharedRead and sharedWrite throughout
my code base. I actually have a tweaked set of Tango's atomics in my
D2 code base. We can't forget CAS and atomic increment/decrement for
shared data.
>
>> 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.
It's worse than that. Notice how in Sean's code that he didn't replace
the sharedWrite call with a more conventional assignment. One really
wants to keep full protection with (most) lock free variables. This
means always using sharedRead and sharedWrite with them. The compiler
should facilitate this...
>
More information about the dmd-concurrency
mailing list