January 04, 2010
On Jan 4, 2010, at 5:00 PM, Andrei Alexandrescu wrote:

> Sean Kelly wrote:
> 
>> 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.

I meant in a shared instance of A.  Basically, most of my uses of shared amount to setting a value that I want to be read lock-free by the user.  I often don't even care about a prompt update of the field, so long as it happens eventually.  I generally just don't want to pay for a lock or possibly deal with contention to read an often checked but rarely changed value.
January 04, 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...
>
January 04, 2010
Le 2010-01-04 ? 18:58, Andrei Alexandrescu a ?crit :

> int y = sharedRead(x);
> ++y;
> sharedWrite(y, x);
> 
> When writing such code the user inevitably hits on the documentation for the two intrinsics, which clearly define their guarantees: only the sequence of sharedRead and sharedWrite is preserved. At that point, inspecting the code and understanding how it works is improved.

I think it could be better than that. Having a lot of functions with various names makes the code much less readable since the logic needs to be expressed in a completely different syntax.

What I'd like is if regular operators could work as atomic operations. It'd be great if ++atomic(y) could translate to atomic increment, atomic(y) = 10 could be used insert a write barrier, and x = atomic(y) could translate to something that reads y with a read barrier. You could even have atomic(y) += 3 for an atomic add. I believe most of this can be achieved with a struct and proper operator overloading.

-- 
Michel Fortin
michel.fortin at michelf.com
http://michelf.com/



January 04, 2010
That looks pretty good!

Andrei

Michel Fortin wrote:
> Le 2010-01-04 ? 18:58, Andrei Alexandrescu a ?crit :
> 
>> int y = sharedRead(x);
>> ++y;
>> sharedWrite(y, x);
>>
>> When writing such code the user inevitably hits on the documentation for the two intrinsics, which clearly define their guarantees: only the sequence of sharedRead and sharedWrite is preserved. At that point, inspecting the code and understanding how it works is improved.
> 
> I think it could be better than that. Having a lot of functions with various names makes the code much less readable since the logic needs to be expressed in a completely different syntax.
> 
> What I'd like is if regular operators could work as atomic operations. It'd be great if ++atomic(y) could translate to atomic increment, atomic(y) = 10 could be used insert a write barrier, and x = atomic(y) could translate to something that reads y with a read barrier. You could even have atomic(y) += 3 for an atomic add. I believe most of this can be achieved with a struct and proper operator overloading.
> 

January 04, 2010
I got what you meant, but in the general case we need to address all possibilities.

So, I think a correct rule is: a shared field is as unwieldy as a global shared. Right?


Andrei

Sean Kelly wrote:
> On Jan 4, 2010, at 5:00 PM, Andrei Alexandrescu wrote:
> 
>> Sean Kelly wrote:
>> 
>>> 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.
> 
> I meant in a shared instance of A.  Basically, most of my uses of shared amount to setting a value that I want to be read lock-free by the user.  I often don't even care about a prompt update of the field, so long as it happens eventually.  I generally just don't want to pay for a lock or possibly deal with contention to read an often checked but rarely changed value. _______________________________________________ dmd-concurrency mailing list dmd-concurrency at puremagic.com http://lists.puremagic.com/mailman/listinfo/dmd-concurrency

January 04, 2010
Sean Kelly wrote:
> I'd planned to ask this a bit later in the discussion, but I've been wondering if all methods need to be labeled as shared or synchronized or if only the public (and possibly protected) ones do?  If I have:
> 
>     class A
>     {
>         void fnA() synchronized { fnB(); } // 1
>         void fnB() shared { fnC(); } // 2
>         private void fnC() {}
>     }
> 
> Since fnC() may only be accessed through A's public interface, all of which is ostensibly safe, does fnC() have to be synchronized or shared?  I can see an argument for not allowing case 2, but it does seem safe for fnC() to be called by fnA() at least.  I know D uses recursive locks so there's no functional barrier to making fnC() synchronized, but this seems like it could be horribly slow.  I suppose the compiler could elide additional lock_acquire() calls though, based on static analysis.

We've considered using protection levels for inferring thread-related checks. One problem is that protection is suspended within the same module, which blunts its strength a lot.

Speaking of lock acquisition - Walter, one thing you may want to consider is to not insert synchronization code inside a method. Instead, put it at the call site. That way you have control over and you can optimize around lock calls.

>> Within such a shareable object, we can use low-level stuff like mutexes, semaphores and conditions to build the desired behaviour, wrapping it up and presenting a clean interface.
> 
> Since mutexes can override a class' monitor, it already works to do this:
> 
>     class A
>     {
>         this() {
>             mut = new Mutex( this );
>             cond = new Condition( mut );
>         }
> 
>         void fnA() synchronized { // locks "mut"
>             cond.wait(); // unlocks the lock acquired when fnA() was entered and blocks, etc.
>         }
> 
>         Mutex mut; Condition cond;
>     }

I didn't know you can do that. Where is Mutex documented? Does it steal/replace or supplement class' built-in mutex?

> I'm hoping this library-level trick will be enough to allow most of the classic synchronization mechanisms to be used in D 2.0.
> 
>> Re synchronized containers - I don't like the idea at all. That is going down the path of having many shared objects, which is notoriously difficult to get right, especially for non-experts. IMO, shared objects should be small in number, and serve as boundaries between threads, which otherwise play in their own separate sand-pits.
> 
> A good pathological but functionally correct example is the Thread class in core.thread.  Thread instances should really be labeled as shared, but I know that when this happens the compiler will throw a fit.  The thing is, while some of the methods are already synchronized, there are quite a few which are not and don't need to be and neither do they need the variables they access to be made lock-free.  Even weirder is:

Well I think Thread is a threading primitive so we shouldn't expect to be implementable without some oddities.


Andrei
January 04, 2010
Yup.

On Jan 4, 2010, at 6:21 PM, Andrei Alexandrescu wrote:

> I got what you meant, but in the general case we need to address all possibilities.
> 
> So, I think a correct rule is: a shared field is as unwieldy as a global shared. Right?
> 
> 
> Andrei
> 
> Sean Kelly wrote:
>> On Jan 4, 2010, at 5:00 PM, Andrei Alexandrescu wrote:
>>> Sean Kelly wrote:
>>>> 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.
>> I meant in a shared instance of A.  Basically, most of my uses of
>> shared amount to setting a value that I want to be read lock-free by
>> the user.  I often don't even care about a prompt update of the
>> field, so long as it happens eventually.  I generally just don't want
>> to pay for a lock or possibly deal with contention to read an often
>> checked but rarely changed value. _______________________________________________ dmd-concurrency
>> mailing list dmd-concurrency at puremagic.com http://lists.puremagic.com/mailman/listinfo/dmd-concurrency
> 
> _______________________________________________
> dmd-concurrency mailing list
> dmd-concurrency at puremagic.com
> http://lists.puremagic.com/mailman/listinfo/dmd-concurrency

January 04, 2010
On Jan 4, 2010, at 6:52 PM, Andrei Alexandrescu wrote:

> Sean Kelly wrote:
>> I'd planned to ask this a bit later in the discussion, but I've been wondering if all methods need to be labeled as shared or synchronized or if only the public (and possibly protected) ones do?  If I have:
>>    class A
>>    {
>>        void fnA() synchronized { fnB(); } // 1
>>        void fnB() shared { fnC(); } // 2
>>        private void fnC() {}
>>    }
>> Since fnC() may only be accessed through A's public interface, all of which is ostensibly safe, does fnC() have to be synchronized or shared?  I can see an argument for not allowing case 2, but it does seem safe for fnC() to be called by fnA() at least.  I know D uses recursive locks so there's no functional barrier to making fnC() synchronized, but this seems like it could be horribly slow.  I suppose the compiler could elide additional lock_acquire() calls though, based on static analysis.
> 
> We've considered using protection levels for inferring thread-related checks. One problem is that protection is suspended within the same module, which blunts its strength a lot.

Yeah, I thought about this and wasn't sure if it was fair to assert that if the user calls private methods then he should hopefully know what he's doing.  But static analysis should even work here (for the last point in my comment above).  You'd just have:

    class A {
        void fnA() synchronized { fnB(); } // 1
        private void fnB() synchronized {}
    }

    void mutateA( A val ) {
        val.fnB(); // 2
    }

At 1, the compiler can easily tell that the lock is already held so it doesn't acquire the lock again.  At 2 the lock is (probably) not held, so it's acquired again.  Since access to privates doesn't extend beyond the scope of the current module, the compiler should have all the information it needs to elide redundant locks via private method calls.

> Speaking of lock acquisition - Walter, one thing you may want to consider is to not insert synchronization code inside a method. Instead, put it at the call site. That way you have control over and you can optimize around lock calls.

Walter had mentioned that methods may have multiple entry points, I believe.  I don't know how common it is to use this approach though (I'd presume not very).

>>> Within such a shareable object, we can use low-level stuff like mutexes, semaphores and conditions to build the desired behaviour, wrapping it up and presenting a clean interface.
>> Since mutexes can override a class' monitor, it already works to do this:
>>    class A
>>    {
>>        this() {
>>            mut = new Mutex( this );
>>            cond = new Condition( mut );
>>        }
>>        void fnA() synchronized { // locks "mut"
>>            cond.wait(); // unlocks the lock acquired when fnA() was entered and blocks, etc.
>>        }
>>        Mutex mut; Condition cond;
>>    }
> 
> I didn't know you can do that. Where is Mutex documented? Does it steal/replace or supplement class' built-in mutex?

I've been very lazy about integrating the docs for Druntime into the Phobos docs,  but the comments are there if you look at src/core/sync/mutex.d.  In short, if you construct a Mutex with an Object argument then it makes that mutex the object's monitor (I believe there's an assert in there to make sure the object has no monitor yet as well).  Originally, I did this so objects could be constructed in shared memory and still use synchronized, but it's a nifty general solution.  The rest of the logic is in src/object_.d, if you're interested.  I consider the design to be one of my better D hacks.

>> I'm hoping this library-level trick will be enough to allow most of the classic synchronization mechanisms to be used in D 2.0.
>>> Re synchronized containers - I don't like the idea at all. That is going down the path of having many shared objects, which is notoriously difficult to get right, especially for non-experts. IMO, shared objects should be small in number, and serve as boundaries between threads, which otherwise play in their own separate sand-pits.
>> A good pathological but functionally correct example is the Thread class in core.thread.  Thread instances should really be labeled as shared, but I know that when this happens the compiler will throw a fit.  The thing is, while some of the methods are already synchronized, there are quite a few which are not and don't need to be and neither do they need the variables they access to be made lock-free.  Even weirder is:
> 
> Well I think Thread is a threading primitive so we shouldn't expect to be implementable without some oddities.

Sounds good.  Hopefully, it will be uniquely horrifying.
January 05, 2010
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/dmd-concurrency/attachments/20100105/33b62989/attachment.htm>
January 05, 2010
An HTML attachment was scrubbed...
URL: <http://lists.puremagic.com/pipermail/dmd-concurrency/attachments/20100105/e04fef43/attachment.htm>