June 01, 2012
On 06/01/12 14:26, deadalnix wrote:
> Le 31/05/2012 20:17, Andrei Alexandrescu a écrit :
>> On 5/31/12 5:19 AM, deadalnix wrote:
>>> The solution consisting in passing a delegate as parameter or as template is superior, because it is now clear who is in charge of the synchronization, reducing greatly chances of deadlock.
>>
>> It can also be a lot clunkier for certain abstractions. Say I want a ProducerConsumerQueue. It's much more convenient to simply make it a synchronized class with the classic primitives, instead of primitives that accept delegates etc.
>>
>> Nevertheless I think there's merit in this idea. One thing to point out is that the idiom can easily be done today with a regular class holding a synchronized class private member.
>>
>> So we got everything we need.
>>
>>
>> Andrei
> 
> I was thinking about that. Here is what I ended up to think is the best solution :
> 
> synchronized classes exists. By default, they can't be use as parameter for synchronized(something) .
> 
> synchronized(something) will be valid is something provide opSynchronized(scope delegate void()) or something similar. Think opApply here. The synchronized statement is rewritten in a call to that delegate.

This has similar issues as opApply. It would have to be a template and always inlined; The opLock/opUnlock approach lets you do the same things w/o the delegate overhead and signature restrictions while making it a bit harder to screw up the locking.

> It open door for stuff like :
> ReadWriteLock rw;
> synchronized(rw.read) {
> 
> }
> 
> synchronized(rw.write) {
> 
> }
> 
> And many types of lock : spin lock, interprocesses locks, semaphores, . . . And all can be used with the synchronized syntax, and without exposing locking and unlocking primitives.
> 
> What do people think ?

It can already be done using 'synchronized', it's *only* an issue of efficiency and syntax. Eg right now i'm doing

      { scope s = somesemaphore.sync;
         whatever();
      }

and a properly lowered 'synchronized' would turn that into

      synchronized (somesemaphore) {
         whatever();
      }

Currently, the latter is probably possible, but not w/o a huge
perf hit; the former is practically free, just as if it was written
as:
    somesemaphore.wait();
    try whatever();
    finally somesemaphore.post();

Lowering 'synchronized' is about making the second form possible, for anything resembling some kind of synchronization primitive. OpSynchronized isn't necessary.

artur
June 01, 2012
Le 01/06/2012 14:52, Steven Schveighoffer a écrit :
> On Fri, 01 Jun 2012 08:38:45 -0400, Dmitry Olshansky
> <dmitry.olsh@gmail.com> wrote:
>
>> On 01.06.2012 16:26, deadalnix wrote:
>>> Here is what I ended up to think is the best
>>> solution :
>>>
>>> synchronized classes exists. By default, they can't be use as parameter
>>> for synchronized(something) .
>>>
>>> synchronized(something) will be valid is something provide
>>> opSynchronized(scope delegate void()) or something similar. Think
>>> opApply here. The synchronized statement is rewritten in a call to that
>>> delegate.
>>>
>>> Here are the benefit of such an approach :
>>> 1/ Lock and unlock are not exposed. You can only use them by pair.
>>> 2/ You cannot lock on any object, so you avoid most liquid locks and
>>> don't waste memory.
>>> 3/ synchronized classes ensure that a class can be shared and it
>>> internal are protected from concurrent access.
>>> 4/ It is not possible possible by default to lock on synchronized
>>> classes's instances. It grant better control over the lock and it is now
>>> clear which piece of code is responsible of it.
>>> 5/ The design allow the programmer to grant the permission to lock on
>>> synchronized classes's instances if he/she want to.
>>> 6/ It is now possible to synchronize on a broader range of user defined
>>> stuffs.
>>>
>>> The main drawback is the same as opApply : return (and break/continue
>>> but it is less relevant for opSynchronized). Solution to this problem
>>> have been proposed in the past using compiler and stack magic.
>>>
>>> It open door for stuff like :
>>> ReadWriteLock rw;
>>> synchronized(rw.read) {
>>>
>>> }
>>>
>>> synchronized(rw.write) {
>>>
>>> }
>>>
>>> And many types of lock : spin lock, interprocesses locks, semaphores, .
>>> . . And all can be used with the synchronized syntax, and without
>>> exposing locking and unlocking primitives.
>>>
>>> What do people think ?
>>
>> +1. Works for me.
>>
>> It refines what I believe the shadow cabinet (loosely: me, you, Alex,
>> Regan Heath and Steven) propose.
>
> Is this really necessary? When is opSynchronized going to be written any
> way other than:
>
> _mutex.lock();
> scope(exit) _mutex.unlock();
> dg();
>
> I'll note that it's easier to forget to lock or unlock if the compiler
> isn't enforcing it. You might even naively do this:
>
> _mutex.lock();
> dg();
> _mutex.unlock(); // not called on exception thrown!
>
> I kind of like the __lock() __unlock() pair that the compiler always
> calls both in the right place/way. Yes, you could just leave those
> implementations blank, but very unlikely.
>
> Plus, we already have issues with inout and delegates for opApply, this
> would have the same issues.
>
>> P.S. Removing monitor from non-synced/shared classes would be good
>> too. As a separate matter.
>
> I think at this point, we should leave it there until we can really
> figure out a detailed plan on how to deal with it. It currently affects
> all runtime code which does virtual function lookups or interface
> lookups, and alignment. We would have to change a lot of compiler and
> runtime code to remove it.
>

A lot of work have to be done, for sure. But concurency is the next big thing D will have to face IMO. When const/immutable are fixed :D
June 01, 2012
On Jun 1, 2012, at 5:26 AM, deadalnix wrote:
> 
> The main drawback is the same as opApply : return (and break/continue but it is less relevant for opSynchronized). Solution to this problem have been proposed in the past using compiler and stack magic.
> 
> It open door for stuff like :
> ReadWriteLock rw;
> synchronized(rw.read) {
> 
> }
> 
> synchronized(rw.write) {
> 
> }

Opens the door?  This works today exactly as outlined above.  Or am I missing a part of your argument?

> And many types of lock : spin lock, interprocesses locks, semaphores, . . . And all can be used with the synchronized syntax, and without exposing locking and unlocking primitives.

All works today.
June 03, 2012
Le 01/06/2012 22:55, Sean Kelly a écrit :
> On Jun 1, 2012, at 5:26 AM, deadalnix wrote:
>>
>> The main drawback is the same as opApply : return (and break/continue but it is less relevant for opSynchronized). Solution to this problem have been proposed in the past using compiler and stack magic.
>>
>> It open door for stuff like :
>> ReadWriteLock rw;
>> synchronized(rw.read) {
>>
>> }
>>
>> synchronized(rw.write) {
>>
>> }
>
> Opens the door?  This works today exactly as outlined above.  Or am I missing a part of your argument?
>
>> And many types of lock : spin lock, interprocesses locks, semaphores, . . . And all can be used with the synchronized syntax, and without exposing locking and unlocking primitives.
>
> All works today.

Unless you do some monitor magic, it doesn't.
June 03, 2012
On Sun, Jun 3, 2012 at 12:29 PM, deadalnix <deadalnix@gmail.com> wrote:

> Le 01/06/2012 22:55, Sean Kelly a écrit :
>
>> On Jun 1, 2012, at 5:26 AM, deadalnix wrote:
>>
>>>
>>> The main drawback is the same as opApply : return (and break/continue
>>> but it is less relevant for opSynchronized). Solution to this problem have
>>> been proposed in the past using compiler and stack magic.
>>>
>>> It open door for stuff like :
>>> ReadWriteLock rw;
>>> synchronized(rw.read) {
>>>
>>> }
>>>
>>> synchronized(rw.write) {
>>>
>>> }
>>>
>>
>> Opens the door?  This works today exactly as outlined above.  Or am I missing a part of your argument?
>>
>>  And many types of lock : spin lock, interprocesses locks, semaphores, .
>>> . . And all can be used with the synchronized syntax, and without exposing locking and unlocking primitives.
>>>
>>
>> All works today.
>>
>
> Unless you do some monitor magic, it doesn't.
>

Yes, it does.
-----
class Something {
    private:
        ReadWriteLock _rw;
    public:
        this() {
            _rw = new ReadWriteLock();
        }
        void doSomething() shared {
            synchronized(_rw.read) {
                // do things
            }
        }
}
-----

I've used this pattern in code. There might be some casting required because the core synchronization primitives haven't been updated to use shared yet.


June 03, 2012
Am 30.05.2012 11:11, schrieb deadalnix:
> 
> D already have much better tools that the one java provide (std.concurency, std.parallelism, TLS by default, transitive type qualifiers, . . .) that most these thing taken from java don't make any sense now.
> 
> For instance, what is the point of being able to lock on any object when most of them are thread local ??

Right! Locking on non-TLS objects doesn't make sense. Perhaps only shared objects should be synchronizeable and thus contain a monitor / pointer to a monitor.
June 03, 2012
Am 31.05.2012 08:47, schrieb Jacob Carlborg:
> What should have been done is something like this:
> 
> 1. Designing feature X
> 2. Show the new feature for the community
> 3. Consider the feedback and possible tweak/redesign
> 4. Implementing in an experimental branch of the compiler
> 5. Release an experimental version with just this feature
> 6. Repeat step 3-5 until satisfied or put on hold/drop the idea
> 7. Prepare Phobos and druntime for the new feature
> 8. Move the implementation to the main branch
> 9. Ship feature X with the next release
> 10. wait
> 11. Fix bugs for feature X
> 12. Repeat step 10-11 a couple of times
> 13. Write about it in TDPL
> 

+1
June 03, 2012
Le 03/06/2012 21:40, Andrew Wiley a écrit :
> On Sun, Jun 3, 2012 at 12:29 PM, deadalnix <deadalnix@gmail.com
> <mailto:deadalnix@gmail.com>> wrote:
>
>     Le 01/06/2012 22:55, Sean Kelly a écrit :
>
>         On Jun 1, 2012, at 5:26 AM, deadalnix wrote:
>
>
>             The main drawback is the same as opApply : return (and
>             break/continue but it is less relevant for opSynchronized).
>             Solution to this problem have been proposed in the past
>             using compiler and stack magic.
>
>             It open door for stuff like :
>             ReadWriteLock rw;
>             synchronized(rw.read) {
>
>             }
>
>             synchronized(rw.write) {
>
>             }
>
>
>         Opens the door?  This works today exactly as outlined above.  Or
>         am I missing a part of your argument?
>
>             And many types of lock : spin lock, interprocesses locks,
>             semaphores, . . . And all can be used with the synchronized
>             syntax, and without exposing locking and unlocking primitives.
>
>
>         All works today.
>
>
>     Unless you do some monitor magic, it doesn't.
>
> Yes, it does.
> -----
> class Something {
>      private:
>          ReadWriteLock _rw;
>      public:
>          this() {
>              _rw = new ReadWriteLock();
>          }
>          void doSomething() shared {
>              synchronized(_rw.read) {
>                  // do things
>              }
>          }
> }
> -----
> I've used this pattern in code. There might be some casting required
> because the core synchronization primitives haven't been updated to use
> shared yet.

And where is that ReadWriteLock ?
June 04, 2012
On Sun, Jun 3, 2012 at 4:39 PM, deadalnix <deadalnix@gmail.com> wrote:

> Le 03/06/2012 21:40, Andrew Wiley a écrit :
>
>> On Sun, Jun 3, 2012 at 12:29 PM, deadalnix <deadalnix@gmail.com <mailto:deadalnix@gmail.com>> wrote:
>>
>>    Le 01/06/2012 22:55, Sean Kelly a écrit :
>>
>>        On Jun 1, 2012, at 5:26 AM, deadalnix wrote:
>>
>>
>>            The main drawback is the same as opApply : return (and
>>            break/continue but it is less relevant for opSynchronized).
>>            Solution to this problem have been proposed in the past
>>            using compiler and stack magic.
>>
>>            It open door for stuff like :
>>            ReadWriteLock rw;
>>            synchronized(rw.read) {
>>
>>            }
>>
>>            synchronized(rw.write) {
>>
>>            }
>>
>>
>>        Opens the door?  This works today exactly as outlined above.  Or
>>        am I missing a part of your argument?
>>
>>            And many types of lock : spin lock, interprocesses locks,
>>            semaphores, . . . And all can be used with the synchronized
>>            syntax, and without exposing locking and unlocking primitives.
>>
>>
>>        All works today.
>>
>>
>>    Unless you do some monitor magic, it doesn't.
>>
>> Yes, it does.
>> -----
>> class Something {
>>     private:
>>         ReadWriteLock _rw;
>>     public:
>>         this() {
>>             _rw = new ReadWriteLock();
>>         }
>>         void doSomething() shared {
>>             synchronized(_rw.read) {
>>                 // do things
>>             }
>>         }
>> }
>> -----
>> I've used this pattern in code. There might be some casting required because the core synchronization primitives haven't been updated to use shared yet.
>>
>
> And where is that ReadWriteLock ?
>

On the GC heap, just like the Monitor object pointed to by __monitor if you mark a method or class as synchronized.


June 04, 2012
Le 04/06/2012 02:03, Andrew Wiley a écrit :
> On Sun, Jun 3, 2012 at 4:39 PM, deadalnix <deadalnix@gmail.com
> <mailto:deadalnix@gmail.com>> wrote:
>
>     Le 03/06/2012 21:40, Andrew Wiley a écrit :
>
>         On Sun, Jun 3, 2012 at 12:29 PM, deadalnix <deadalnix@gmail.com
>         <mailto:deadalnix@gmail.com>
>         <mailto:deadalnix@gmail.com <mailto:deadalnix@gmail.com>>> wrote:
>
>             Le 01/06/2012 22:55, Sean Kelly a écrit :
>
>                 On Jun 1, 2012, at 5:26 AM, deadalnix wrote:
>
>
>                     The main drawback is the same as opApply : return (and
>                     break/continue but it is less relevant for
>         opSynchronized).
>                     Solution to this problem have been proposed in the past
>                     using compiler and stack magic.
>
>                     It open door for stuff like :
>                     ReadWriteLock rw;
>                     synchronized(rw.read) {
>
>                     }
>
>                     synchronized(rw.write) {
>
>                     }
>
>
>                 Opens the door?  This works today exactly as outlined
>         above.  Or
>                 am I missing a part of your argument?
>
>                     And many types of lock : spin lock, interprocesses
>         locks,
>                     semaphores, . . . And all can be used with the
>         synchronized
>                     syntax, and without exposing locking and unlocking
>         primitives.
>
>
>                 All works today.
>
>
>             Unless you do some monitor magic, it doesn't.
>
>         Yes, it does.
>         -----
>         class Something {
>              private:
>                  ReadWriteLock _rw;
>              public:
>                  this() {
>                      _rw = new ReadWriteLock();
>                  }
>                  void doSomething() shared {
>                      synchronized(_rw.read) {
>                          // do things
>                      }
>                  }
>         }
>         -----
>         I've used this pattern in code. There might be some casting required
>         because the core synchronization primitives haven't been updated
>         to use
>         shared yet.
>
>
>     And where is that ReadWriteLock ?
>
> On the GC heap, just like the Monitor object pointed to by __monitor if
> you mark a method or class as synchronized.

I meant where is the code ?