|
Boost : |
From: Howard Hinnant (hinnant_at_[hidden])
Date: 2003-09-27 16:15:17
Hello,
I'm looking for design guidelines for using boost::threads along with
the singleton pattern. I'm not concerned with the threadsafe
construction of the singleton. Rather I want to return a smart pointer
to the singleton that also acts as a lock so that only one thread at a
time can access the singleton.
Something like:
struct X
{
void foo() {}
};
struct HandleDeleter
{
...
void operator(X*) {mut_.unlock(); owns_ = false;}
...
};
typedef boost::shared_ptr<X> Handle;
Handle singleton()
{
static X x;
static boost::mutex mut;
return Handle(&x, HandleDeleter(mut));
}
void bar()
{
{
Handle h = singleton();
// other threads block on call to singleton
// until h destructs
h->foo();
} // singleton lock released
}
Several questions:
1. Does this seem like a reasonable user interface for singleton?
Clients would have the ability to hold the Handle indefinitely which
might cause thread contention, but I suspect that this will be true no
matter what.
I'm not wanting to put the lock inside of the operator-> of the handle
as the client (bar) will in general need to call several functions on X
atomically.
I could make a separate get_singleton() / release_singleton() calls
(which would lock and unlock), but that seems clumsy and unsafe from an
exception safety point of view.
So assuming the answer to question 1 is yes,
2. What is the best (cleanest, most efficient, whatever) way to
implement this interface?
For the moment I'm neglecting the problem of multiple threads racing to
create any statics in singleton(). I know how to solve that problem,
and ignoring it for now simplifies the discussion.
shared_ptr<X> is used as an example smart pointer above, but I'm not yet
convinced that this is the best way to go. A variant of auto_ptr that
allowed for a customized deletion policy would probably be a better
choice. Afterall, only one instance of the handle should retain
ownership of the singleton mutex. But the smart pointer must be able to
be returned from a function (copyable), so scoped_ptr isn't viable.
Assuming the existence of some auto_ptr<X, D> (or whatever), I still
have a problem in that the custom deleter can't really traffic directly
in boost::mutex because there is no (public) interface to boost::mutex
that could lock and unlock.
I then thought that the deleter could use mutex::scoped_lock instead.
But that doesn't really work either. That gets me the lock and unlock
functions, but where or how do you store the scoped_lock? It can't be a
static of singleton, because the second thread coming in trying to lock
the static singleton (when it is already locked) will throw instead of
block. It can't be a local of singleton because it will unlock the
mutex right after you return the smart pointer. It can't be a member of
the deleter or smart pointer because the smart pointer must be copyable
(with move semantics) and scoped_lock can neither copy nor transfer
ownership of its mutex.
I finally came up with a working solution involving two mutexes, two
scoped_locks, and one condition:
Handle singleton()
{
static mutex mut;
static mutex mut2;
static scoped_lock lk(mut, false);
static condition c;
static X x;
scoped_lock guard(mut2);
while (lk)
c.wait(guard);
lk.lock();
return Handle(&x, HandleDeleter(lk, c));
}
And when the Handle destructs, the HandleDeleter both unlocks lk, and
calls notify_one on the condition.
The HandleDeleter is copyable because it refers back to the static mutex
and condition of singleton instead of copying them internally. And
Handle is an auto_ptr knock off that takes a deleter.
Is it just me, or is this much harder (and more expensive) than it needs
to be? Am I missing some simpler, lighter weight solution?
Better ideas?
Thanks,
-Howard
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk