|
Boost : |
From: Chris Thomasson (cristom_at_[hidden])
Date: 2006-11-04 02:01:02
"Roland Schwarz" <roland.schwarz_at_[hidden]> wrote in message
news:454A9E56.8050901_at_chello.at...
> Chris Thomasson wrote:
>> Here is one without a ctor:
>
> It still has a dtor. Same as Anmthony already said applies to dtor too.
>
> You will end up needing to drop the class and use a function with a
> flag I suspect.
Yup. I think so...
>
> Btw.: The usage scenario you were showing earlier is not quite what
> we had in mind. We were speaking about local statics.
Ahhh... Okay, I was thinking global.
> In your example usage, you have moved the static part
> to global namespace. There are far more less problems.
Indeed! :^)
> The problems are getting nasty when needing local statics
> as in
>
> void foo()
> {
> static bar mybar;
>
> }
>
> You really might need them, because the static possibly
> depending an a template parameter, which makes it impossible
> to move it to global namespace.
>
> So you will need to be able to declare the once class inside the
> function body scope.
I see.
> Back to ctor/dtor. Once you have found out that ctors and dtors
> are bad, what is left? Well several options:
>
> 1) a once function and a flag (instead of a class)
> 2) a static initializeable mutex
> 1) and 2) are roughly equivalent in that one can be built out of
> the other.
Agreed. However, one can be lock-free after the initialization takes
place... IMHO, this is a fairly important aspect of this type of algorithm.
I am going to give my pseudo-code one more shot at the end of the post in
the form of compliable code that uses my AppCore library. This time, no
class, just a single C function. I urge you to bear with me, and try to take
a quick look at it... Thank you all for your time and patience!
:^)
> 1) has been used in pthreads (before a static mutex type was available)
> Boost.Thread also uses this also mainly in implementation code.
>
> 2) I have tried to find out if it is possible to implement a mutex
> that is statically initializeable with the additional constraint
> that zero initialization suffices.
> I have provided a prototype that let me believe it really can be
> done. (Such a mutex has the additional benefit that you need no
> explicit memory management.)
You can do a spinlock for sure... How are you setting the mutex's waitset?
Are you deferring waitset allocation until first point of contention (e.g.,
lazy mutex) ?
Okay here is a link to AppCore:
http://appcore.home.comcast.net/
And here is the once code; please tell me what you think of my technique:
once-experimental.cpp
---------------
#include <cassert>
#include <cstdio>
#include <appcore.h>
// User API Decl
namespace atomic {
template<typename T> class once_ptr;
} // namespace atomic
// System API Decl
namespace atomic {
namespace sys {
typedef ac_intword_t refs_t;
template<typename T> struct once_POD;
template<typename T> struct once_def_POD;
static bool once_inc(refs_t*) throw();
static bool once_dec(refs_t*) throw();
static void dbg_allocs_inc() throw();
static void dbg_allocs_dec() throw();
}} // namespace atomic::sys
// System API Def
namespace atomic {
namespace sys {
// Debug counter
static ac_intword_t dbg_allocs = 0;
// Inc the debug counter
void dbg_allocs_inc() throw() {
ac_intword_t allocs = ac_atomic_inc_acquire(&dbg_allocs);
std::printf("atomic::sys::dbg_allocs_inc - %i\n", allocs);
}
// Inc the debug counter
void dbg_allocs_dec() throw() {
ac_intword_t allocs = ac_atomic_dec_acquire(&dbg_allocs);
std::printf("atomic::sys::dbg_allocs_dec - %i\n", allocs);
}
// Inc the refcount if its >= 0.
// returns true if the refcount was inc'd,
// otherwise false
bool once_inc(refs_t *_this) throw() {
refs_t local, cmp;
do {
local = ::ac_mb_load_naked(_this);
if (local < 0) { return false; }
cmp = local;
local = ac_atomic_cas_acquire(_this, local, local + 1);
} while(cmp != local);
std::printf("atomic::sys::once_inc(); - %i\n", local + 1);
return true;
}
// Dec the refcount if its >= 0.
// returns true for the last ref,
// otherwise false
bool once_dec(refs_t *_this) throw() {
refs_t local, cmp;
do {
local = ::ac_mb_load_naked(_this);
if (local < 0) { return false; }
cmp = local;
local = ac_atomic_cas_acquire(_this, local, local - 1);
} while(cmp != local);
std::printf("atomic::sys::once_dec(); - %i\n", local - 1);
if (local == 1) {
ac_mb_store_release(_this, 0);
return true;
} else if (! local) {
return true;
}
return false;
}
// once is a POD
#define ATOMIC_ONCE_SYS_STATICINIT() {0, 0}
template<typename T>
struct once_POD {
typedef T type_t;
typedef once_def_POD<once_POD> define_POD_t;
refs_t m_refs;
type_t *m_state;
};
// define is a POD that holds a once POD
template<typename T>
struct once_def_POD {
typedef typename T::type_t type_t;
static T s_this;
// Acquires a reference and calls ctor for first ref.
// returns pointer to ref,
// otherwise NULL
type_t* acquire() {
type_t *local = (type_t*)ac_mb_loadptr_depends(&s_this.m_state);
if (! local) {
// hashed_mutex::guard_t lock(&_this);
local = (type_t*)ac_mb_loadptr_depends(&s_this.m_state);
if (! once_inc(&s_this.m_refs)) { return 0; }
if (! local) {
try { local = new type_t; }
catch(...) {
(void)once_dec(&s_this.m_refs);
throw;
}
dbg_allocs_inc();
std::printf("\n(%p)once_def_POD::acquire(); - %p\n", (void*)this,
(void*)local);
ac_mb_storeptr_release(&s_this.m_state, local);
}
} else if(! once_inc(&s_this.m_refs)) {
return 0;
}
return local;
}
// Releases a reference and calls dtor if last ref.
// returns nothing,
// otherwise false
void release() {
type_t *local = (type_t*)ac_mb_loadptr_depends(&s_this.m_state);
if (once_dec(&s_this.m_refs)) {
if (local) {
ac_atomic_casptr_acquire(&s_this.m_state, local, 0);
delete local;
std::printf("(%p)once_def_POD::release(); - %p\n\n", (void*)this,
(void*)local);
dbg_allocs_dec();
}
}
}
// static POD init
}; template<typename T>
T once_def_POD<T>::s_this = ATOMIC_ONCE_SYS_STATICINIT();
}} // namespace atomic::sys
// User API Def
namespace atomic {
// Holds an acquired reference.
template<typename T>
class once_ptr {
public:
typedef typename sys::once_POD<T>::define_POD_t define_POD_t;
private:
define_POD_t *m_once;
T *m_state;
private:
T* acquire() {
return (m_once) ? m_once->acquire() : 0;
}
void release() {
if (m_once) { assert(m_state); m_once->release(); }
}
public:
once_ptr() throw() : m_once(0), m_state(0) {}
once_ptr(define_POD_t &_once)
: m_once(&_once), m_state(_once.acquire()) {}
~once_ptr() { release(); }
public:
once_ptr(once_ptr const &rhs)
: m_once(rhs.m_once), m_state(rhs.acquire()) {}
once_ptr const& operator =(once_ptr &rhs) {
define_POD_t *old = m_once;
if (old != rhs.m_once) {
define_POD_t *_once = rhs.m_once;
T *_state = rhs.acquire();
release();
m_once = _once;
m_state = _state;
}
return *this;
}
once_ptr const& operator =(define_POD_t &rhs) {
define_POD_t *old = m_once;
if (old != &rhs) {
define_POD_t *_once = &rhs;
T *_state = rhs.acquire();
release();
m_once = _once;
m_state = _state;
}
return *this;
}
public:
T* load() const throw() { return m_state; }
public:
T* operator ->() { return load(); }
T& operator *() { return *load(); }
public:
operator bool() throw() { return (m_state != 0); }
bool operator !() throw() { return (m_state == 0); }
};
} // namespace atomic
// Here is sample usage:
struct foo1 {
typedef atomic::once_ptr<foo1> once_ptr_t;
void whatever() {
std::printf("(%p)foo1::whatever();\n", (void*)this);
}
foo1() { std::printf("(%p)foo1::foo1();\n", (void*)this); }
~foo1() { std::printf("(%p)foo1::~foo1();\n", (void*)this); }
};
static void funca_for_multiple_threads() {
static foo1::once_ptr_t::define_POD_t s_foo;
foo1::once_ptr_t myfoo1(s_foo);
if (myfoo1) {
myfoo1->whatever();
}
}
struct foo2 {
typedef atomic::once_ptr<foo1> once_ptr_t;
void whatever() {
std::printf("(%p)foo2::whatever();\n", (void*)this);
funca_for_multiple_threads();
}
foo2() {
static foo1::once_ptr_t::define_POD_t s_foo;
foo1::once_ptr_t myfoo1(s_foo);
if (myfoo1) {
myfoo1->whatever();
}
std::printf("(%p)foo2::foo2();\n", (void*)this);
}
~foo2() { std::printf("(%p)foo2::~foo2();\n", (void*)this); }
};
struct foo3 {
typedef atomic::once_ptr<foo3> once_ptr_t;
static foo1::once_ptr_t::define_POD_t s_foo1;
void whatever() {
std::printf("(%p)foo3::whatever();\n", (void*)this);
foo1::once_ptr_t myfoo1(s_foo1);
if (myfoo1) {
myfoo1->whatever();
}
funca_for_multiple_threads();
}
foo3() {
static foo2::once_ptr_t::define_POD_t s_foo2;
foo2::once_ptr_t myfoo2(s_foo2), myfoo2a;
if (myfoo2) {
myfoo2->whatever();
foo1::once_ptr_t myfoo1(s_foo1);
myfoo2a = myfoo2;
if (myfoo1) {
myfoo1->whatever();
}
myfoo2a->whatever();
}
std::printf("(%p)foo3::foo3();\n", (void*)this);
}
~foo3() {
whatever();
std::printf("(%p)foo3::~foo3();\n", (void*)this);
}
}; foo1::once_ptr_t::define_POD_t foo3::s_foo1;
static void funcb_for_multiple_threads() {
funca_for_multiple_threads();
static foo2::once_ptr_t::define_POD_t s_myfoo2a;
funca_for_multiple_threads();
foo2::once_ptr_t smyfooa(s_myfoo2a), smyfooaa;
if (smyfooa) {
smyfooa->whatever();
}
{
funca_for_multiple_threads();
static foo2::once_ptr_t::define_POD_t s_myfoo2b;
foo1 myfoo1;
foo2::once_ptr_t smyfooa(s_myfoo2a);
if (smyfooa) {
smyfooaa = smyfooa;
smyfooaa->whatever();
}
foo2 myfoo2;
{
myfoo1.whatever();
foo2::once_ptr_t smyfoo2(s_myfoo2a);
if (smyfoo2) {
smyfoo2->whatever();
}
myfoo2.whatever();
funca_for_multiple_threads();
{
foo3 myfoo3;
myfoo3.whatever();
foo2 myfoo2;
myfoo2.whatever();
}
myfoo2.whatever();
}
foo3 myfoo3;
foo2::once_ptr_t smyfoo2(s_myfoo2b);
if (smyfoo2) {
smyfoo2->whatever();
funca_for_multiple_threads();
myfoo3.whatever();
}
funca_for_multiple_threads();
}
if (smyfooaa) {
smyfooaa->whatever();
}
funca_for_multiple_threads();
}
int main(int argc, char *argv[])
{
funca_for_multiple_threads();
funcb_for_multiple_threads();
funca_for_multiple_threads();
funcb_for_multiple_threads();
return 0;
}
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk