Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

A real threaded singleton

0 views
Skip to first unread message

EvilO...@googlemail.com

unread,
Apr 9, 2007, 1:23:09 PM4/9/07
to
In the stuff I've read about multi threaded singleton it seems to
assume that creating the Lock is atomic, and various sources,
including Sutter go for RAII.
{
Lock l;
if ( ! inst )....
}
But I don't see how to do this with a real O/S API.
The ones I've looked at have a two step process to making a lock, they
require a "Create" function and a "Start Locking" function.

A Lock class is presumably implemented as something like:
class Lock
{
private :
PrimitiveLockType PrimitiveLock;
PrimitiveHandle handle;


public:
Lock() {
PrimitiveLock = CreatePrimitiveLock():
handle = StartLocking (PrimitiveLock);
}
~Lock()
{
StopLocking(handle);
KillPrimitiveLock(PrimitiveLock);
}

};

Now if I have two threads there is a chance (albeit a small one) that
I end up creating two PrimitiveLocks

Thus far, I've got around it by making the PrimitiveLock a global
static like the Instance member.
That makes error handling awkward, though if this is failing at such
an early stage then the program's not likely to work anyway.
What am I missing ?


}

Chris Thomasson

unread,
Apr 9, 2007, 4:37:20 PM4/9/07
to

<EvilO...@googlemail.com> wrote in message
news:1176139389....@n76g2000hsh.googlegroups.com...

> In the stuff I've read about multi threaded singleton it seems to
> assume that creating the Lock is atomic, and various sources,
> including Sutter go for RAII.

There is a solution for a thread-safe version of that here:

http://groups.google.com/group/comp.programming.threads/browse_frm/thread/6da701564880ab4

http://appcore.home.comcast.net/vzdoc/atomic/static-init/

http://appcore.home.comcast.net/vzoom/refcount/

You use a hashed-mutex/spinlock scheme to handle atomic lock
construction/destruction. e.g.:

// Details
#define DETAIL_CONCATX(__t1, __t2)__t1##__t2
#define DETAIL_CONCAT(__t1, __t2)CONCATX(__t1, __t2)

#define DETAIL_HASHMUTEX_8(__init)\
__init, __init, __init, __init, __init, __init, __init, __init

#define DETAIL_HASHMUTEX_32(__init)\
DETAIL_HASHMUTEX_8(__init), DETAIL_HASHMUTEX_8(__init),\
DETAIL_HASHMUTEX_8(__init), DETAIL_HASHMUTEX_8(__init)

#define DETAIL_HASHMUTEX_128(__init)\
DETAIL_HASHMUTEX_32(__init), DETAIL_HASHMUTEX_32(__init),\
DETAIL_HASHMUTEX_32(__init), DETAIL_HASHMUTEX_32(__init)

#define DETAIL_HASHMUTEX_256(__init)\
DETAIL_HASHMUTEX_128(__init), DETAIL_HASHMUTEX_128(__init)

#define DETAIL_HASHMUTEX_512(__init)\
DETAIL_HASHMUTEX_256(__init), DETAIL_HASHMUTEX_256(__init)

#define DETAIL_HASHMUTEX_CONCAT(__init, __count)\
DETAIL_CONCAT(DETAIL_HASHMUTEX_, __count)(__init)


// Impl
#include <pthread.h>

#define HASHMUTEX_DEPTH() 256
#define HASHMUTEX_INITIALIZER() PTHREAD_MUTEX_INITIALIZER

#define HASHMUTEX_PTR(__ptr) (((int)__ptr) % HASHMUTEX_DEPTH())

#define HASHMUTEX_STATICINIT() {\
DETAIL_HASHMUTEX_CONCAT(HASHMUTEX_INITIALIZER(), HASHMUTEX_DEPTH())\
}

// hashed-mutex table
static pthread_mutex_t hashmutex_table[
HASHMUTEX_DEPTH()
] = HASHMUTEX_STATICINIT();

static int hashmutex_lock(void* const ptr) {
return pthread_mutex_lock(&hashmutex_table[HASHMUTEX_PTR(ptr)]);
}

static int hashmutex_trylock(void* const ptr) {
return pthread_mutex_trylock(&hashmutex_table[HASHMUTEX_PTR(ptr)]);
}

static int hashmutex_unlock(void* const ptr) {
return pthread_mutex_unlock(&hashmutex_table[HASHMUTEX_PTR(ptr)]);
}


class A {};
class B {};

static A g_a;
static B g_b;


int main() {
// lock & unlock a
hashmutex_lock(&g_a);
// ...
hashmutex_unlock(&g_a);

// lock & unlock b
hashmutex_lock(&g_b);
// ...
hashmutex_unlock(&g_b);
return 0;
}

Got to watch out for deadlocks... Here is one possible solution:

http://groups.google.com/group/comp.programming.threads/browse_frm/thread/e0c011baf08844c4/

Any thoughts?


:^)

James Kanze

unread,
Apr 10, 2007, 10:48:43 AM4/10/07
to
On Apr 9, 7:23 pm, EvilOld...@googlemail.com wrote:
> In the stuff I've read about multi threaded singleton it seems to
> assume that creating the Lock is atomic, and various sources,
> including Sutter go for RAII.
> {
> Lock l;
> if ( ! inst )....}

> But I don't see how to do this with a real O/S API.

> The ones I've looked at have a two step process to making a lock, they
> require a "Create" function and a "Start Locking" function.

Any real answer will depend on the OS. Normally, you should be
able to create and initialize a mutex as a static object. If
not, you need a global mutex, which is used to synchronize the
creation of all of the secondary mutexes, and some tricky way of
initializing it.

In my own code, I generally find it acceptable to require that
threads don't start until after entering main, which means in
practice that the initialization of objects with static
lifetime, at least those which aren't in a dynamically linked
object, has occured. Thus, if you use a singleton, all you have
to do to ensure that it doesn't need a lock is to ensure that at
least one call to the instance function occurs during static
initialization. Something like:

bool dummyForInitialization = Singleton::instance(), tru ;

does the trick, as does:

Singleton* Singleton::ourInstance = &Singleton::instance() ;

> A Lock class is presumably implemented as something like:
> class Lock
> {
> private :
> PrimitiveLockType PrimitiveLock;
> PrimitiveHandle handle;

> public:
> Lock() {
> PrimitiveLock = CreatePrimitiveLock():
> handle = StartLocking (PrimitiveLock);}

> ~Lock()
> {
> StopLocking(handle);
> KillPrimitiveLock(PrimitiveLock);
> }
> };

No. An RAII "lock" class is always based on an external mutex
or some other external synchronization primitive. You still
have the problem of creating that external object, of course.

> Now if I have two threads there is a chance (albeit a small one) that
> I end up creating two PrimitiveLocks

> Thus far, I've got around it by making the PrimitiveLock a global
> static like the Instance member.

That's normally what you do.

> That makes error handling awkward, though if this is failing at such
> an early stage then the program's not likely to work anyway.

If creating the lock fails, you have a problem. As you say, it
occurs at such an early stage that the program isn't likely to
work anyway, so you just abort with an error message.

--
James Kanze (GABI Software) email:james...@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

0 new messages