On 30-05-2012 15:10, Regan Heath wrote:
On Wed, 30 May 2012 13:43:14 +0100, deadalnix <deadal...@gmail.com> wrote:
Le 30/05/2012 14:32, Regan Heath a écrit :
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.
Indeed, and can be made optimal with some fine-tuning/work.
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.
True, I was just mentioning it as an option that solves the key
issue/problem - not as the best solution to that problem. I think #1 is
better.
Ultimately the "problem" that needs solving is the fact that
synchronized classes/methods use a public mutex and this exposure makes
deadlocks more likely. I was hoping that by giving some examples in
code, it would become clearer to the people who don't see what all the
fuss is about.
Yes, having to manually create a mutex is a pain, and as Andrei has
mentioned a few times having 2 separate things (one object to lock, one
mutex) which conceptually should be one thing is worse from an
understanding/maintenance point of view, however..
1. If the mutex is intended to lock a single object then we can make the
following options available:
a. A Lockable class to derive from.
b. A Lockable struct to compose with (assuming here that the struct will
be created/destroyed with the object).
c. A Lockable!(T) wrapper template/struct/class to wrap objects to be
locked.
d. A Lockable interface which classes may implement.
e. Some sort of compiler magic where it supplies the mutex for
objects/methods marked with "synchronized" or involved in a
synchronized(object) statement.
2. If the scope of the locking is greater than a single object, then a
separate mutex is required/desired anyway.
1a. would contain a mutex primitive and lock/tryLock/unlock methods (it
would implement the interface in 1d)
1b. would also contain a mutex primitive and alias could be used to
expose the lock/tryLock/unlock methods (it would implement the interface
in 1d)
1c. is actually a technique I have used a lot in C++ where the template
class is created on the stack of a function/critical section and it
(optionally) locks on creation (or later when lock() is called) and
always unlocks on destruction as it leaves scope (by any means). It's a
very robust pattern, and is similar (the inspiration/or result) of/to
the synchronized block itself.
In D, you can kind of already do that:
void foo()
{
mtx.lock();
scope (exit) mtx.unlock();
// ...
}
It does involve that one extra scope (exit) line, but I'm of the opinion
that being explicit about that is a good thing.
1d. could be a requirement for classes which are to be used in
synchronized statements i.e.
class A : Lockable
{
void lock() {}
void unlock() {}
}
A a = new A();
synchronized(a)
{ // calls a.lock()
...
} // calls a.unlock()
1e. I leave as a opener to Walter/Andrei/the reader .. I've seen many a
genius idea pop out of nowhere on this forum.
IMO, the wasted space used by the monitor isn't ideal, but it's not a
"problem" for the general case. It is a problem for people with limited
memory environments but that's another topic. Talking about both is only
clouding the discussional waters so to speak.
R
--
Alex Rønne Petersen
a...@lycus.org
http://lycus.org