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

Regan Heath regan at netmail.co.nz
Wed May 30 05:32:28 PDT 2012


On Wed, 30 May 2012 10:21:00 +0100, deadalnix <deadalnix at gmail.com> wrote:

> Le 30/05/2012 00:53, Andrei Alexandrescu a écrit :
>> On 5/29/12 3:01 PM, Alex Rønne Petersen wrote:
>>> On 29-05-2012 23:54, Andrei Alexandrescu wrote:
>>>> On 5/29/12 2:49 PM, Alex Rønne Petersen wrote:
>>>>> On 29-05-2012 23:32, Andrei Alexandrescu wrote:
>>>>>> On 5/29/12 1:35 AM, deadalnix wrote:
>>>>>>> Le 29/05/2012 01:38, Alex Rønne Petersen a écrit :
>>>>>>>> I should probably add that Java learned it long ago, and yet we
>>>>>>>> adopted
>>>>>>>> it anyway... blergh.
>>>>>>>>
>>>>>>>
>>>>>>> That is what I was about to say. No point of doing D if it is to
>>>>>>> repeat
>>>>>>> previously done errors.
>>>>>>
>>>>>> So what is the lesson Java learned, and how does it address
>>>>>> multithreaded programming in wake of that lesson?
>>>>>>
>>>>>> Andrei
>>>>>
>>>>> It learned that allowing locking on arbitrary objects makes  
>>>>> controlling
>>>>> locking (and thus reducing the chance for deadlocks) impossible.
>>>>
>>>> And how does Java address multithreading in general, and those issues  
>>>> in
>>>> particular, today?
>>>>
>>>> Andrei
>>>>
>>>
>>> It doesn't, and neither does C#. Java still encourages using
>>> synchronized, and C# still encourages using lock, but many prominent
>>> figures in those programming language communities have written blog
>>> posts on why these language constructs are evil and should be avoided.
>>
>> Some citations (beyond the known fallacies of Java 1.0) would be great.
>> Thanks.
>>
>>> Besides, it seems to me that D can't quite make up its mind. We have  
>>> TLS
>>> by default, and we encourage message-passing (through a library
>>> mechanism), and then we have the synchronized statement and attribute.
>>> It just seems so incredibly inconsistent. synchronized encourages doing
>>> the wrong thing (locks and synchronization).
>>
>> Each paradigm has its place. Lock-based programming is definitely here
>> to stay, and when the paradigm is needed, doing it with synchronized
>> objects is better than most alternatives.
>>
>>
>> Andrei
>
> No !
>
> You don't want to synchronize on ANY object. You want to synchronize on  
> explicit mutexes.

+1 .. this is the key point/issue.  If all objects can be locked, and if  
synchronized classes/methods use the /same/ mutex you can  
easily/accidentally have hard to find deadlocks.  The deadlocks in Q are  
usually (at least) two threads locking (at least) two objects in the  
opposite order and with synchronized classes/methods the fact that a lock  
is being taken is not always obvious from the code (at the call site) so  
the problem code can look fairly innocuous.

**[Example 1]**

class C
{
   synchronized void ccc() { }
}

class D
{
   synchronized void ddd() { }
}

C c = new C();
D d = new D();

[thread1]
..lock(c)  // locks c
{          // deadlock window
   d.ddd(); // locks d
}

[thread2]
..lock(d)  // locks d
{          // deadlock window
   c.ccc(); // locks c
}

**[Example 2]**

class A
{
   B b;
   ..
   synchronized void foo()  // locks a
   {                        // deadlock window
     b.bar();               // locks b
   }
}

class B
{
   A a;
   ..
   synchronized void bar()  // locks b
   {                        // deadlock window
     a.foo();               // locks a
   }
}

A a = new A();
B b = new B();

a.b = b;
b.a = a;

[thread1]
a.foo(); // locks a then b

[thread2]
b.bar(); // locks b then a

So, the root cause of the problem is that synchronized classes/methods use  
a publicly visible mutex (the object/this pointer) to perform their  
locking.  The solution is to lock on a private mutex, which no-one else  
can lock unexpectedly as described in the link below:

> See that link for instance :  
> http://msdn.microsoft.com/en-us/library/ms173179.aspx

Now, ideally we want to make it hard for people to screw this up in this  
manner and there are several ways to do it.

1. Prevent locking on any/every object.  People would have to create a  
separate mutex object for locking.  This is a little tedious, but it does  
make people think about the scope of the locking (where to put the mutex,  
who can see/use it, etc).  On the flipside it can make people re-use a  
mutex for multiple conceptual critical section areas of code, which is  
less than ideal, but at least it's not 'broken'.

2. Allow locking on any/every object but use a different mutex for  
synchronized class/methods.  So 'synchronized' on a method call locks a  
private mutex object, not the object itself.  And synchronized(object)  
locks the public mutex object.  The downside here is that now every object  
potentially has 2 mutex objects/handles internally - a public and a  
private one.

.. there are probably a number of other ways to do it.  I favour #1,  
especially as thread-local is the default storage type now.  100% of  
objects in a single threaded application don't need a built-in mutex, most  
objects (80%??) in a threaded application will be thread-local and don't  
need one.  Only those few shared objects, which are actually manually  
locked with synchronized or have synchronized class/methods actually need  
a mutex.  It seems that removing this will save memory and avoid tricky  
deadlock bugs and the cost is having to manually create a mutex object  
(1/2 lines of code) to lock instead.. it seems like a good idea to me.

R

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


More information about the Digitalmars-d mailing list