synchronized (this[.classinfo]) in druntime and phobos

Regan Heath regan at netmail.co.nz
Thu May 31 07:01:26 PDT 2012


On Thu, 31 May 2012 12:13:00 +0100, Andrei Alexandrescu  
<SeeWebsiteForEmail at erdani.org> wrote:
> On 5/31/12 3:27 AM, Regan Heath wrote:
>> I think
>> the mutex is "available for locking and unlocking" <- need a word for
>> that, which is not "exposed". How about accessible, available, usable,
>> or just plain lockable .. So, the problem here is that the mutex is
>> lockable by external code via synchronized() and this means a certain
>> type of deadlock is possible.
>
> But this is a protection/visibility issue, which is orthogonal on the  
> locking capability. It's as if you say "int is not good because anyone  
> can overflow it." Okay! Make it private inside a CheckedInt class.

Sorry, that's a bad comparison.  CheckedInt is to int, what CheckedMutex  
is to mutex - but I'm not suggesting anything like a CheckedMutex.  I'm  
suggesting "mutex" but kept private inside the class /that it locks/.   
Yes, it's a visibility issue, the issue is that the mutex used by  
synchronized classes/methods is too visible/accessible and this opens it  
up for deadlocks which are otherwise impossible.

>> I think the change mentioned in TDPL to restrict synchronized to
>> synchronized classes is a step in the right direction WRT wasted monitor
>> space and people freely locking anything. But, it is exactly the case
>> which results in more possible deadlocks (the cause of this thread) AND
>> I think it's actually far more likely people will want to use a
>> synchronized statement on a class which is not itself synchronized, like
>> for example an existing container class.
>>
>> Given that, restricting synchronized statements to synchronized classes
>> seems entirely wrong to me.
>
> So where's the mutex that would be used to synchronize objects that are  
> not synchronizable?

In the wrapper class/struct/object which derives a synchronized  
class/struct from the original.  My D foo is not strong enough to just  
come up with valid D code for the idiom on the fly, but essentially you  
wrap the original object in a new object using a template which adds the  
mutex member and the interface methods (lock, tryLock, and unlock)  
required.  No, this doesn't work with "final" classes.. but it shouldn't,  
they're final after all.  For them you need to add/manage the mutex  
manually - the price you pay for "final".

>> In fact, I would say you almost want to stop
>> people using synchronized statements on synchronized classes because:
>> 1. If a synchronized class is written correctly it should not be
>> necessary in the general case(*)
>> 2. It raises the chances of deadlocks (the cause of this thread).
>> 3. It means that classes in general will be simpler to write (no need to
>> worry about synchronization) and also more lightweight for use in
>> non-threaded/non-shared cases. (because we'd provide a template wrapper
>> to make them synchronizable)
>
> There are cases in which you want to do multiple operations under a  
> single critical section, even though the API is otherwise well-designed.  
> That may be for correctness, efficiency, or both. I don't see why we'd  
> want to disallow that, it's a good idiom.

Who suggested disallowing this?  No-one.  There are 3 main use cases I see  
for this;

1. Several disparate objects locked by a single mutex - in which case the  
correct solution is a separate mutex/monitor object.
2. A single object, locked for serveral method calls - in which case the  
method-passed-a-delegate idea (mentioned below/ described in a separate  
thread) works, unless..
3. The calls span several scopes, in which case good-old manual  
lock/unlock is required (synchronized blocks don't help here)

>> So, more and more I'm thinking it would be better to provide a
>> library/runtime co-operative solution where we have an interface which
>> is required for synchronized statements, and a wrapper template to
>> implement that interface for any existing non-synchronized class.
>> Meaning, the choice is either to write a synchronized class (rare) or a
>> non-synchronized class - knowing it can easily be synchronized if/when
>> needed.
>
> Looking forward for a fleshed out proposal. Make sure you motivate it  
> properly.

Sorry, I have no spare time to spare.  You're getting free ideas/thoughts  
 from me, feel free to ignore them.

>> The "liquid lock" problem mentioned earlier is an interesting one that I
>> have not personally experienced, perhaps because I don't lock anything
>> but mutex primitives and I never to re-assign these.
>>
>> (*) Locking on a larger scope can be achieved by providing a method
>> taking a delegate (see earlier thread/replies) and/or exposing
>> lock/unlock for those few situations where the delegate method cannot be
>> used. These are the few cases in which this cannot be avoided as the
>> lock/unlock are separated by more than a single scope (so synchronized
>> statements don't help in these cases either).
>
> On first look, the inversion of control using delegates delegates has  
> similar liabilities as straight scoped locking.

True, it's basically the same as a synchronized block in that respect.   
What we actually want is a way to limit the calls made by the delegate to  
methods of the object itself.  If it could not call a synchronized method  
on a 2nd object, you could not possibly deadlock.  Except, that is to say,  
unless you held a separate lock beforehand - but, the important point here  
is that you would have to take both locks explicitly, rather than by an  
implicit synchronized method call, making the bug far more obvious to code  
inspection.

R

-- 
Using Opera's revolutionary email client: http://www.opera.com/mail/


More information about the Digitalmars-d mailing list