[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