On Wed, 30 May 2012 10:21:00 +0100, deadalnix <deadal...@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/

Reply via email to