Boost logo

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