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

How to multithread intrusive reference counting

20 views
Skip to first unread message

John Dill

unread,
Jul 23, 2003, 7:28:22 AM7/23/03
to
I have an implementation of an example of my attempt to implement
intrusive reference counting and copy-on-write behavior, derived from
Andrei and Meyers versions of intrusive reference counting. I am
looking for help determining a good way to make this setup thread
safe. I apologize for the length of the post, but don't have a
website available to me to post stuff.

Here is my version of COMRefCounted, which is a little more verbose
than Loki's.

/*!
* \class IntrusiveReferenceCounted smartptr.hpp
* \brief Implements an interface for intrusive reference counting.
*/
template <class P>
class IntrusiveReferenceCounted
{
public:

//! Do nothing constructor.
IntrusiveReferenceCounted()
{}

//! Do nothing copy constructor.
template <class P1>
IntrusiveReferenceCounted( const IntrusiveReferenceCounted<P1>& )
{}

//! Adds a reference to the reference count (stored in the pointee
//! object). Performs a shallow copy of the pointer object.
static P Clone( const P& ptr )
{
ptr->AddReference();
return ptr;
}

//! Removes a reference from the reference count (stored in the
//! pointee object). The object itself is responsible for cleanup.
static bool Release( const P& ptr )
{
ptr->RemoveReference();
return false;
}

//! Swap function.
static void Swap( IntrusiveReferenceCounted& )
{}

//! A flag to determine whether the Colvin-Gibbons trick is needed.
enum { destructiveCopy = false };
};

For the intrusive reference counting mechanism, I followed something
similar to that presented by Meyers I believe.

/*!
* \class ReferenceCountedObject smartptr.hpp
* \brief Base class for an intrusive reference counted class.
*
* This implements a reference counting interface for a class which
* desires intrusive reference counting where the reference count is
* stored in the object.
*
* The reference counting scheme differs from the SmartPtr policy
* version in that if the reference count for an object goes to zero,
* the deletion of the object is done through 'delete this'. The
* 'delete this' statement calls the destructor of the object
* (hence the requirement of a virtual destructor). The reference
* count is not manipulated by this classes member functions because
* that behavior is controlled by the smartptr.
*/
class ReferenceCountedObject
{
public:

//! Increments the reference count.
void AddReference()
{ ++count_; }

//! Decrements the reference count and deletes the object if no
//! references exist.
void RemoveReference()
{
if ( --count_ == 0 ) {
delete this;
}
}

protected:

//! Initializes the reference count to 1.
ReferenceCountedObject()
: count_( 1 )
{}

//! Initializes the reference count to 1.
ReferenceCountedObject( const ReferenceCountedObject& )
: count_( 1 )
{}

//! Do nothing destructor, but necessary to call the destructor of a
//! derived class which uses this reference counting scheme.
virtual ~ReferenceCountedObject()
{}

//! Do nothing assignment operator.
ReferenceCountedObject& operator=( const ReferenceCountedObject& )
{ return *this; }

private:

//! The reference count.
int count_;
};


/*!
* \class COWReferenceCountedObject smartptr.hpp
* \brief Base class for an intrusive reference counted class for
* implementing copy-on-write (COW) behavior.
*
* This implements a reference counting interface for a class which
* desires intrusive reference counting where the reference count is
* stored in the object. In addition, mechanisms for implementing
* copy-on-write reference counting behavior exist but it is the
* class's responsibility to ensure that copy-on-write behavior is
* maintained.
*
* The reference counting scheme differs from the SmartPtr policy
* version in that if the reference count for an object goes to zero,
* the deletion of the object is done through 'delete this'. The
* 'delete this' statement calls the destructor of the object
* (hence the requirement of a virtual destructor). The reference
* count is not manipulated by this classes member functions because
* that behavior is controlled by the smartptr.
*/
class COWReferenceCountedObject
{
public:

//! Increments the reference count.
void AddReference()
{ ++count_; }

//! Decrements the reference count and deletes the object if no
//! references exist.
void RemoveReference()
{
if ( --count_ == 0 ) {
delete this;
}
}

/*!
* \brief Marks an object as unshareable.
*
* When an operation is performed which has the capability of
* modifying the object (any member function which is non-const),
* the object is marked as unshareable. This is a safe way of
* insuring that the copy-on-write implementation is maintained.
*/
void MarkUnshareable()
{ shareable_ = false; }

//! Asks if the object is shareable.
bool IsShareable() const
{ return shareable_; }

//! Asks if the object is shared between different pointers.
bool IsShared() const
{ return count_ > 1; }

protected:

//! Initializes the reference count to 1.
COWReferenceCountedObject()
: count_( 1 ), shareable_( true )
{}

//! Initializes the reference count to 1.
COWReferenceCountedObject( const ReferenceCountedObject& )
: count_( 1 ), shareable_( true )
{}

//! Do nothing destructor, but necessary to call the destructor of a
//! derived class which uses this reference counting scheme.
virtual ~COWReferenceCountedObject()
{}

//! Do nothing assignment operator.
COWReferenceCountedObject& operator=( const
COWReferenceCountedObject& )
{ return *this; }

private:

//! The reference count.
int count_;

//! A flag which tells if the object has not been modified.
bool shareable_;
};


Here are the corresponding implementations of a "very" simple string
class which tests intrusive reference counting.


class RCString
{
public:

RCString( const char* initValue = "" )
: value_( new StringValue( initValue ) )
{}

char operator[]( int index ) const
{ return value_->data[index]; }

char& operator[]( int index )
{ return value_->data[index]; }

char** GetPtr()
{ return &(value_->data); }

friend inline char** GetRCStringImpl( RCString& rcstring )
{ return rcstring.GetPtr(); }

private:

struct StringValue
: public Loki::ReferenceCountedObject
{
char* data;

StringValue( const char* initValue )
{ init( initValue ); }

StringValue( const StringValue& rhs )
{ init( rhs.data ); }

~StringValue()
{ delete [] data; }

void init( const char* initValue )
{
data = new char[strlen( initValue ) + 1];
strcpy( data, initValue );
}

friend class Loki::SmartPtr<StringValue,
Loki::IntrusiveReferenceCounted>;
};

Loki::SmartPtr<StringValue, Loki::IntrusiveReferenceCounted> value_;

inline friend std::ostream& operator<<( std::ostream& os, const
RCString& string )
{ return os << string.value_->data; }

};


class COWString
{
public:

COWString( const char* initValue = "" )
: value_( new StringValue( initValue ) )
{}

char operator[]( int index ) const
{ return value_->data[index]; }

char& operator[]( int index )
{
if ( value_->IsShared() ) {
value_ = new StringValue( value_->data );
}
value_->MarkUnshareable();

return value_->data[index];
}

char** GetPtr()
{ return &(value_->data); }

friend inline char** GetCOWStringImpl( COWString& cowstring )
{ return cowstring.GetPtr(); }

private:

struct StringValue
: public Loki::COWReferenceCountedObject
{
char* data;

StringValue( const char* initValue )
{ init( initValue ); }

StringValue( const StringValue& rhs )
{ init( rhs.data ); }

~StringValue()
{ delete [] data; }

void init( const char* initValue )
{
data = new char[strlen( initValue ) + 1];
strcpy( data, initValue );
}

friend class Loki::SmartPtr<StringValue,
Loki::IntrusiveReferenceCounted>;
};

Loki::SmartPtr<StringValue, Loki::IntrusiveReferenceCounted> value_;

inline friend std::ostream& operator<<( std::ostream& os, const
COWString& string )
{ return os << string.value_->data; }

};


Hope this is enough information to be able to show where the
synchronization needs to be made from those who have experience with
this. I don't intend the RCString and COWString to be robust string
classes, but as examples to test the correct behvaior. If there are
any other issues that are known from this also, I would like to learn
about them. I also have not done much in the way of exception testing
with this code at all, so if there's any obvious exception problems
I'd like to know about them too. If there is additional information
needed, let me know.

Many thanks,
John

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Alexander Terekhov

unread,
Jul 23, 2003, 7:28:01 PM7/23/03
to

John Dill wrote:
[...]

> char& operator[]( int index )
> {
> if ( value_->IsShared() ) {
> value_ = new StringValue( value_->data );
> }
> value_->MarkUnshareable();
>
> return value_->data[index];
> }

Nah, http://google.com/groups?selm=3E4B6227.87DCBA45%40web.de

char& operator[](size_t index) {
switch (IntAtomicGet(data_->refs)) {
case 01: pthread_refcount_setvalue(&data_->refs, 0);
case 00: break;
default: data_ = new StringBuf(CharRefStringBufPtr(data_));
}
return data_->buf[index];
}

oder?

regards,
alexander.

-- ("brilliant thoughts"...)

"You [the Linux user] are taking on the copyright and intellectual
property risk in exchange for lower cost. Now you may think that
is worth it, but the second SCO comes at the end users, that risk
might become more real. That does not mean that open source is not
a great model; it is a great model. We've been doing open source
forever."
-- http://www.eweek.com/article2/0,3959,1200764,00.asp

John Dill

unread,
Jul 23, 2003, 8:13:25 PM7/23/03
to
After searching a little on COW and MT, I came across Herb Sutter's
COW and MT comparison. I think I will stay away from COW and MT in
this case, and to make the intrusive reference counting work, all that
should be needed is to make the count atomic. I am curious how
counted_base in Boost is used. Is it used in COW, or just intrusive
reference counting from those that use it? Thanks for any info.

Richard Smith

unread,
Jul 23, 2003, 8:14:25 PM7/23/03
to
John Dill wrote:

> I have an implementation of an example of my attempt to implement
> intrusive reference counting and copy-on-write behavior, derived from
> Andrei and Meyers versions of intrusive reference counting. I am
> looking for help determining a good way to make this setup thread
> safe.

First you need to work out what you mean by "thread safe".
As Andrei Alexandrescu points out in section 7.13 of "Modern
C++ Design",

"The interaction between smart pointers and multithreading
takes place at two levels. One is the pointee object
level, and the other is the bookkeeping data level."

Thread safety at a bookkeeping level is, in my opinion,
essential to any smart pointer that might be used in
multithread code. It is about guaranteeing that copying a
smart pointer is as safe as copying a raw pointer, and all
that you need to do for ReferenceCountedObject is ensure
that there is no race condition between your AddReference
and RemoveReference functions.

Thread safety at a pointee object level is a different
matter. It effectively involves locking the object whilst a
function call is active on it. In my experience this is
often the wrong level to be putting locking.

Returning to making AddReference / RemoveReference thread
safe, the issue is simply one of preventing a race condition
between ++count_ and (--count_ == 0). Neither of these are
guaranteed to happen atomically w.r.t. threads, and indeed
with many compilers they will not be. There are basically
two approaches to making them atomic.

The first approach is to slap a mutex the critical region
of code. Here I've done it using the Boost.Thread library
[http://www.boost.org/libs/threads], which I personally like
and is relatively portable, but you could use some other
threading library's mutexes.

class ReferenceCountedObject {
public:
void AddReference() {
boost::mutex::scoped_lock lk(mtx_);
++count_;
}

void RemoveReference() {
bool do_delete;
{
boost::mutex::scoped_lock lk(mtx_);
do_delete = --count_ == 0;
}
if (do_delete) delete this;
}

// Other members
private:
boost::mutex mtx_;
int count_;
};

Note that the "delete this" is not part of the critical
region and so has been moved out of the locked region.

A variant of this would be to use a spinlock instead of a
mutex. Typically you would expect a spinlock to be faster
than a mutex in the limit of no lock contention.

The second approach is to use raw assembler to ensure the
increment and decrement-and-test happen atomically.
Fortunately, it is rarely necessary to write the assembler
by hand as many platforms provide such functionality.
Linux does this in <asm/atomic.h>, Windows provides
InterlockedIncrement / InterlockedDecrement. This is likely
to be significantly more efficient than either a mutex or a
spin-lock. It will also save you the storage space of a
mutex.

(If you find you do need to implement these atomic
operations from scratch and you're using the Intel x86
architecture, chapter 7.1 of volume 3 of the IA-32
development manual is a good starting place.)


Moving on to the COW classes. First, what is shareable_
supposed to do? It's never read in your code. (A fine
application for Signetics' write-only memory. ;-)
[http://www.ganssle.com/misc/wom1.jpg]) I presume the
intention is that you cannot increment the reference count
of a COW object that's been marked unsharedable. If so,
you'll need to do a bit more work. For a start you'll need
a new StoragePolicy similar to IntrusiveReferenceCounted but
that is able to do a deep copy if the class is unshareable.
Here's a (non- thread safe) sketch:

template <class P>
class COWIntrusiveReferenceCounted {
public:
COWIntrusiveReferenceCounted() {}

template <class P1>
COWIntrusiveReferenceCounted
( COWIntrusiveReferenceCounted const& ) {}

static P Clone( P const& ptr ) {
if ( ! ptr->IsShareable() )
return ptr->Clone(); // do a deep copy
ptr->AddReference();
return ptr;
}

static bool Release( P const& ptr ) {
ptr->RemovedReference(); return false;
}

static void Swap( COWIntrusiveReferenceCounted& ) {}
enum { destructiveCopy = false };
};

This requires adding a virtual function

virtual COWReferenceCountedObject*
COWReferenceCountedObject::Clone() const = 0;

that performs a deep copy. This needs to be implemented in
COWString::StringValue.


Now on to the threading issues. Take a look at the code for
"uniquifying" the StringValue:

if ( value_->IsShared() ) {
value_ = new StringValue( value_->data );
}
value_->MarkUnshareable();

In particular, is it possible for IsShared() to return
false, but for the string to become shared before
MarkUnshareable() is called? This depends on how the string
is used. If COWStrings themselves are never shared between
threads, and only their StringValues are, then once the ref
count has fallen to 1 it can only be incremented by the
current thread. If this is the case, then this code is
thread safe.

If, however, a single COWString instance can be shared
between threads (e.g. a global COWString), then there is a
possible race condition here: IsShared() returns false,
another thread makes a copy incrementing the ref count, and
then MarkUnshareable is called. The best solution is
probably to rely on higher level mutexes (or other
synchronisation mechanisms) to protect this.

--
Richard Smith

Alexander Terekhov

unread,
Jul 23, 2003, 10:20:50 PM7/23/03
to

John Dill wrote:
>
> After searching a little on COW and MT, I came across Herb Sutter's
> COW and MT comparison.

Advice: forget it ("Appendix A", that is).

> I think I will stay away from COW and MT in
> this case, and to make the intrusive reference counting work, all that
> should be needed is to make the count atomic.

Yeah.

class sp_counted_base {
/* ... */
typedef refcount<std::size_t, basic> count;
count use_count_, self_count_;
/* ... */
public:
/* ... */
sp_counted_base() : use_count_(1), self_count_(1) { }

std::size_t use_count() const throw() {
return use_count_.get();
}

void add_ref() throw() {
use_count_.increment();
}

bool lock() throw() {
return use_count_.increment_if_not_min();
}

void weak_add_ref() throw() {
self_count_.increment();
}

void weak_release() throw() {
if (!self_count_.decrement(msync::acq, count::may_not_store_min))
destruct();
}

void release() throw() {
if (!use_count_.decrement()) {
dispose();
if (!self_count_.decrement(msync::rel, count::may_not_store_min))
destruct();
}
}
/* ... */
};

http://www.terekhov.de/pthread_refcount_t/experimental/refcount.cpp
(updated recently)

Oder?

regards,
alexander.

George van den Driessche

unread,
Jul 25, 2003, 3:29:16 PM7/25/03
to

"Richard Smith" <ric...@ex-parrot.com> wrote in message
news:Pine.LNX.4.55.03...@sphinx.mythic-beasts.com...
[snip]

> The first approach is to slap a mutex the critical region
> of code. Here I've done it using the Boost.Thread library
> [http://www.boost.org/libs/threads], which I personally like
> and is relatively portable, but you could use some other
> threading library's mutexes.
>
> class ReferenceCountedObject {
> public:
> void AddReference() {
> boost::mutex::scoped_lock lk(mtx_);
> ++count_;
> }
>
> void RemoveReference() {
> bool do_delete;
> {
> boost::mutex::scoped_lock lk(mtx_);
> do_delete = --count_ == 0;
> }
> if (do_delete) delete this;
> }
>
> // Other members
> private:
> boost::mutex mtx_;
> int count_;
> };
>
> Note that the "delete this" is not part of the critical
> region and so has been moved out of the locked region.
>
> A variant of this would be to use a spinlock instead of a
> mutex. Typically you would expect a spinlock to be faster
> than a mutex in the limit of no lock contention.
[snip]

Is there any advantage to moving the deletion out of the lock region?
Surely
by the time you come to delete this, you know that no other threads are
holding references to this any more, which means that no-one is going
to be
waiting for the mutex either. Put another way, if anyone *is* waiting
for
the mutex then you're screwed anyway because they're about to use an
object
that you'll have just destroyed.

George

John Dill

unread,
Jul 25, 2003, 4:08:29 PM7/25/03
to
Alexander Terekhov <tere...@web.de> wrote in message
news:<3F1F26CC...@web.de>...
> John Dill wrote:

Thank you for your responses. I hope to glean from you as much
information as I can ;)

>>
>> After searching a little on COW and MT, I came across Herb Sutter's
>> COW and MT comparison.
>
> Advice: forget it ("Appendix A", that is).

Are you saying that COW and MT work better than say RefCount and MT?
Was I overgeneralizing in avoiding COW and MT from Herb's argument?
Or is there something faulty in his analysis? Would you still use COW
and MT for string?

I have looked over that particular boost code and I can't quite
capture why they use two counts in that particular way.

>
> http://www.terekhov.de/pthread_refcount_t/experimental/refcount.cpp
> (updated recently)
>

I took the intrusive reference count and made it atomic, and it
appears to behave according with my synch and lack of synch test
programs. I have a class wrapper over an int which does the locking,
similar in function but different in interface from your example.

> Oder?

You'll have to enlighten me with what "Oder" means :)

Also I've never quite understood why you lock the shared data when you
return by value. Like in this case

// in Atomic<typename T, class ThreadingModel> type class
T get_value() const
{
Guard<ThreadingModel::Mutex> guard; // locks the shared data 'value'
return value;
}

// in some other code, where T is int
int my_value = atomic.get_value();

I assume there are some problems with the stack being synchronized,
but how it behaves is not explicitly known to me from when the guard
releases to where the variable is assigned the result of get_value.

>
> regards,
> alexander.
>

Best,
John

John Dill

unread,
Jul 26, 2003, 6:28:30 AM7/26/03
to
Alexander Terekhov <tere...@web.de> wrote in message news:<3F1F26CC...@web.de>...
> John Dill wrote:

Thank you for your responses. I hope to glean from you as much
information as I can ;)

> >


> > After searching a little on COW and MT, I came across Herb Sutter's
> > COW and MT comparison.
>
> Advice: forget it ("Appendix A", that is).

Are you saying that COW and MT work better than say RefCount and MT?


Was I overgeneralizing in avoiding COW and MT from Herb's argument?
Or is there something faulty in his analysis? Would you still use COW
and MT for string?

>

I have looked over that particular boost code and I can't quite


capture why they use two counts in that particular way.

>
> http://www.terekhov.de/pthread_refcount_t/experimental/refcount.cpp
> (updated recently)
>

I took the intrusive reference count and made it atomic, and it
appears to behave according with my synch and lack of synch test
programs. I have a class wrapper over an int which does the locking,
similar in function but different in interface from your example.

> Oder?

You'll have to enlighten me with what "Oder" means :)

Also I've never quite understood why you lock the shared data when you
return by value. Like in this case

// in Atomic<typename T, class ThreadingModel> type class
T get_value() const
{
Guard<ThreadingModel::Mutex> guard; // locks the shared data 'value'
return value;
}

// in some other code, where T is int
int my_value = atomic.get_value();

I assume there are some problems with the stack being synchronized,
but how it behaves is not explicitly known to me from when the guard
releases to where the variable is assigned the result of get_value.

Best,
John

John Dill

unread,
Jul 26, 2003, 6:56:49 AM7/26/03
to
Richard Smith <ric...@ex-parrot.com> wrote in message news:<Pine.LNX.4.55.03...@sphinx.mythic-beasts.com>...
> John Dill wrote:
>
> > I have an implementation of an example of my attempt to implement
> > intrusive reference counting and copy-on-write behavior, derived from
> > Andrei and Meyers versions of intrusive reference counting. I am
> > looking for help determining a good way to make this setup thread
> > safe.
>
> First you need to work out what you mean by "thread safe".
> As Andrei Alexandrescu points out in section 7.13 of "Modern
> C++ Design",
>
> "The interaction between smart pointers and multithreading
> takes place at two levels. One is the pointee object
> level, and the other is the bookkeeping data level."
>

My primary goal is to get the internals to the reference counting
structure protected, like the reference count itself, and in the COW,
the shareable flag, which I don't even use :P.

> Thread safety at a bookkeeping level is, in my opinion,
> essential to any smart pointer that might be used in
> multithread code. It is about guaranteeing that copying a
> smart pointer is as safe as copying a raw pointer, and all
> that you need to do for ReferenceCountedObject is ensure
> that there is no race condition between your AddReference
> and RemoveReference functions.
>
> Thread safety at a pointee object level is a different
> matter. It effectively involves locking the object whilst a
> function call is active on it. In my experience this is
> often the wrong level to be putting locking.

Are you saying that the locking should not be within an object's
member functions for example, but external to those which use it?

>
> Returning to making AddReference / RemoveReference thread
> safe, the issue is simply one of preventing a race condition
> between ++count_ and (--count_ == 0). Neither of these are
> guaranteed to happen atomically w.r.t. threads, and indeed
> with many compilers they will not be. There are basically
> two approaches to making them atomic.
>
> The first approach is to slap a mutex the critical region
> of code. Here I've done it using the Boost.Thread library
> [http://www.boost.org/libs/threads], which I personally like
> and is relatively portable, but you could use some other
> threading library's mutexes.

I have an atomic int wrapper which when substituted in for the int,
behaves as expected with my test programs.

>
> class ReferenceCountedObject {
> public:
> void AddReference() {
> boost::mutex::scoped_lock lk(mtx_);
> ++count_;
> }
>
> void RemoveReference() {
> bool do_delete;
> {
> boost::mutex::scoped_lock lk(mtx_);
> do_delete = --count_ == 0;
> }
> if (do_delete) delete this;

I do not understand why the 'if (do_delete)' is not locked. Is the if
statement atomic? What about 'if (do_delete != 0)' or the '=='
variant?

> }
>
> // Other members
> private:
> boost::mutex mtx_;
> int count_;
> };
>
> Note that the "delete this" is not part of the critical
> region and so has been moved out of the locked region.

Is this because 'this' is not shared data in this sense?

>
> A variant of this would be to use a spinlock instead of a
> mutex. Typically you would expect a spinlock to be faster
> than a mutex in the limit of no lock contention.
>
> The second approach is to use raw assembler to ensure the
> increment and decrement-and-test happen atomically.
> Fortunately, it is rarely necessary to write the assembler
> by hand as many platforms provide such functionality.
> Linux does this in <asm/atomic.h>, Windows provides
> InterlockedIncrement / InterlockedDecrement. This is likely
> to be significantly more efficient than either a mutex or a
> spin-lock. It will also save you the storage space of a
> mutex.

I will definitely look into this when the functionality appears to be
working. Is the <asm/atomic.h> part of the kernel code, or from gcc?

>
> (If you find you do need to implement these atomic
> operations from scratch and you're using the Intel x86
> architecture, chapter 7.1 of volume 3 of the IA-32
> development manual is a good starting place.)
>
>
> Moving on to the COW classes. First, what is shareable_
> supposed to do? It's never read in your code. (A fine
> application for Signetics' write-only memory. ;-)

I am working on trying to patent that technique myself. I'd get rich
from the number of times people do that :P

> [http://www.ganssle.com/misc/wom1.jpg]) I presume the
> intention is that you cannot increment the reference count
> of a COW object that's been marked unsharedable. If so,
> you'll need to do a bit more work. For a start you'll need
> a new StoragePolicy similar to IntrusiveReferenceCounted but
> that is able to do a deep copy if the class is unshareable.
> Here's a (non- thread safe) sketch:

{Code snipped, please avoid over quoting long posts. -mod}

Thanks for the code. I will look at it and try to better wrap my head
around COW.

{snipped -mod}

Is COW meant only to protect dynamically allocated data? It appears
that it wouldn't work with non-dynamically allocated data.

I have had problems with global variables with Loki SmartPtr, in
particular with the reference counting. When I create a SmartPtr
globally, the SmartPtr gets created and then the SmallObjAllocator
gets created when it needs to allocate the reference count. But on
destruction, the SmallObjAllocator gets destroyed before the
destruction of the global SmartPtr is done with it. I thought the
PhoenixSingleton would solve the problem, but it doesn't appear to :/

Best,
John

Richard Smith

unread,
Jul 27, 2003, 1:20:13 PM7/27/03
to
John Dill wrote:

> My primary goal is to get the internals to the reference counting
> structure protected, like the reference count itself, and in the COW,
> the shareable flag, which I don't even use :P.

This is what Andrei Alexandrescu refers to as "the
bookkeeping data level", and it is a good idea to make this
all thread-safe.

> > Thread safety at a pointee object level is a different
> > matter. It effectively involves locking the object whilst a
> > function call is active on it. In my experience this is
> > often the wrong level to be putting locking.
>
> Are you saying that the locking should not be within an object's
> member functions for example, but external to those which use it?

Yes. Take for example a COW string class. I would not
expect it to be safe against concurrant writes by different
threads.

> > void RemoveReference() {
> > bool do_delete;
> > {
> > boost::mutex::scoped_lock lk(mtx_);
> > do_delete = --count_ == 0;
> > }
> > if (do_delete) delete this;
>
> I do not understand why the 'if (do_delete)' is not locked. Is the if
> statement atomic? What about 'if (do_delete != 0)' or the '=='
> variant?

George van den Driessche quite correctly pointed out that
this is an unnecessary "optimisation". Once the reference
count has fallen to zero, no-one else can use the object and
it doesn't matter how long the mutex remains locked. In
some pointer designs (e.g. those that need to support weak
pointers) the reference count can still be accessed even
after the pointer has been destroyed, and then this
optimisation is important. My apologies for getting these
confused.

As to why 'if (do_delete)' is not locked, it is because
do_delete is an automatic function-scope variable and can't
be accessed by other threads. Once it is set, its value
remains the same, and can be safely accessed outside the
critical region.

> I will definitely look into this when the functionality appears to be
> working. Is the <asm/atomic.h> part of the kernel code, or from gcc?

It's a kernel header.

> Is COW meant only to protect dynamically allocated data? It appears
> that it wouldn't work with non-dynamically allocated data.

I'm not really sure what you mean by "non-dynamically
allocated data". Do you just mean stack allocated data? If
so, the ownership of the memory is governed exclusively by
the function in whose stack frame the data is allocated.
COW doesn't really make much sense in this context, although
I supose you could write a smart pointer that can also
contain stack allocated memory which it then never deletes.

> I have had problems with global variables with Loki SmartPtr, in
> particular with the reference counting. When I create a SmartPtr
> globally, the SmartPtr gets created and then the SmallObjAllocator
> gets created when it needs to allocate the reference count. But on
> destruction, the SmallObjAllocator gets destroyed before the
> destruction of the global SmartPtr is done with it. I thought the
> PhoenixSingleton would solve the problem, but it doesn't appear to :/

Although I can't duplicate it myself, I can guess what's
happening. The Loki::RefCounted policy does not hold onto a
SmallObjAllocator, so there is nothing to prevent it from
being destroyed before the last call to RefCounted::Release.
When the PhoenixSingleton recreates the SmallObjAllocator,
it does it using its default constructor, which causes it to
loose any state it ever had. This (probably) causes the
deallocation to fail.

--
Richard Smith

0 new messages