Le 30/05/2012 14:32, Regan Heath a écrit :
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'.


It is suboptimal, but correct.

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.


It doesn't address most of the drawback cited. Notably the fact that every object have a monitor field, but most of them will not use it.

Reply via email to