Boost logo

Boost :

From: Larry Evans (jcampbell3_at_[hidden])
Date: 2000-09-11 17:45:16


Kaz Kylheku wrote:

> On Sun, 10 Sep 2000 23:19:38 -0500, Larry Evans <jcampbell3_at_[hidden]> wrote:
> >
> >Kaz Kylheku wrote:
> >
> >> On Mon, 28 Aug 2000 20:49:24 +0200, Frederic Chauvin
> >> <frederic.chauvin_at_[hidden]> wrote:
> >> >Hi,
> >> >
> >> >I'm developing a C++ application and which uses extensively smart
> >> >pointers and reference counting in a fashion similar to Microsoft's
> >> >IUnkown::AddRef/Release.
> >> >My question is: how to make this mecanisn thread-safe?
> >>
> >> You must ensure that the reference count increments and decrements are atomic
> >> operations. This means that you need a lock around these operations.
> >
> >Even with atomic increment and decrement, wouldn't there be the race condition
> >described in figs 4&5 of the article at www-sor.inria.fr/publi/SDGC_iwmm95.html ?
>
> No. This paper is talking about distributed reference counting. The particular
> race condition example involves message passing communications.
>
> I was only talking about straightforward reference counting within the address
> space of a threaded process (i.e. squarely within the topic for
> comp.programming.threads).
>
> No such race occurs in this situation because the handoff of a reference
> between one subsystem and another is typically done synchronously. E.g.
> subsystem A holds a reference to an object managed by subsystem B, and wants
> to pass it to C. It calls a function within C which increments the reference.
> Upon returning, A releases its reference. There is no race because everything
> is done in order by the calling thread.

I've attached a file which I hope illustrates why I still don't agree.

Each access to RefCountAny::m_nrefs is made atomic by use of `Sentry s(m_lock)`
in each method which changes m_nrefs.

An example of a thread schedule which causes premature deletion is indicated
by comments of the form:

  //step_<i>:<op><thread_id>

where:
   <i> is an integer indicating execution order
   <thread_id> is either main_t or sub_t which are thread id's
   <op> is either:
       + indicating <thread_id> is resuming execution
       - indicating <thread_id> is yielding to the other thread

Between step_5 and step_6, pointee1 is deleted; however, step_7 and step_8 shows
sub_t still accessing pointee.

Does this convince you, or is there something I'm missing?

Thanks for you patience.


#include <pthread.h>
#include <stdio.h>
//========================================
  class
Lock
  {
  public:
    Lock(void)
      { pthread_mutex_init(&m_mutex,NULL)
      ;}
    ~Lock(void)
      { pthread_mutex_destroy(&m_mutex)
      ;}
      void
    lock(void)
      { pthread_mutex_lock(&m_mutex)
      ;}
      void
    unlock(void)
      { pthread_mutex_unlock(&m_mutex)
      ;}
  private:
      pthread_mutex_t
    m_mutex
      ;
  };
//========================================
  class
Sentry
  {
  public:
    Sentry(Lock&a_lock)
      : m_lock(a_lock)
      {
      ; m_lock.lock()
      ;}
    ~Sentry(void)
      {
      ; m_lock.unlock()
      ;}
  private:
      Lock&
    m_lock
      ;
  };
//========================================
  class
SmartPtr
  ;
  class
RefCountAny
  {
  friend SmartPtr;
  public:
        typedef
      RefCountAny
    ThisType
      ;
        typedef
      unsigned
    NrefsType
      ;
        virtual
    ~RefCountAny(void)
      {
      ;}
    RefCountAny(void)
      : m_nrefs(0)
      {
      ;}
    RefCountAny(RefCountAny const&)
      : m_nrefs(0)
      {
      ;}
      RefCountAny const&
    operator=(RefCountAny const&)
      { return *this
      ;}
  private:
      NrefsType
    inc_nrefs(void)
      {
      ; NrefsType res=0
      //step_4:-sub_t
      //step_7:+sub_t
      ; {
        ; Sentry s(m_lock)
        ; res=++m_nrefs
        ;}
      ; return res
      //step_8:-sub_t
      ;}
      NrefsType
    dec_nrefs(void)
      {
      ; NrefsType res=0
      ; {
        ; Sentry s(m_lock)
        ; res=--m_nrefs
        ; if(res==0) delete this
        ;}
      ; return res
      ;}
      NrefsType
    m_nrefs
      //The number of references to this instance
      ;
        mutable
      Lock
    m_lock
      //To make accesses and changes to m_nrefs atomic
      ;
  };
//========================================
  class
SmartPtr
  {
  public:
        typedef
      SmartPtr
    ThisType
      ;
    SmartPtr(RefCountAny*p=0)
      : pointee_(p)
      { inc_nrefs()
      ;}
    SmartPtr(SmartPtr const&p)
      : pointee_(p.pointee_)
      {
      ; inc_nrefs()
      ;}
    ~SmartPtr()
      { dec_nrefs()
      ;}
      ThisType const&
    operator=(RefCountAny*p)
      {
      ; if(p!=pointee_)
        { dec_nrefs()
        ; pointee_=p
        ; inc_nrefs()
        ;}
      ; return *this
      ;}
      ThisType const&
    operator=(SmartPtr const&sp)
      { return operator=(sp.pointee_)
      ;}
      bool
    valid(void)const
      {
      ; return !(pointee_==0)
      ;}
  private:
      RefCountAny*
    pointee_
      ;
      RefCountAny::NrefsType
    dec_nrefs(void)
      { RefCountAny::NrefsType res=0
      ; if(valid() && (res=pointee_->dec_nrefs()) == 0)
        {
        ; pointee_=0
        ;}
      ; return res
      ;}
      RefCountAny::NrefsType
    inc_nrefs(void)
      { RefCountAny::NrefsType res=0
      ; if(valid())
        {
        ; res=pointee_->inc_nrefs()
        ;}
      ; return res
      ;}
  };
//========================================
  void*
thread_sub(void*v_arg)
  ;
//========================================
  pthread_t
main_t
  //main thread
  ;
  pthread_t
sub_t
  //thread_sub thread
  ;
  int
main(void)
  {
//step_1:+main_t
  ; int ret=0
  ; main_t=pthread_self()
  ; RefCountAny*pointee1=new RefCountAny
  ; SmartPtr sp1(pointee1)
  ; ret=pthread_create(&sub_t,0,thread_sub,&sp1)
  ; if(ret)return ret
//step_2:-main_t
//step_5:+main_t
  ; RefCountAny*pointee2=new RefCountAny
  ; sp1=pointee2
//step_6:-main_t
//step_9:+main_t
  ; ret=pthread_join(sub_t,0)
  ; if(ret)return ret
  ; return 0
  ;}
//========================================
  void*
thread_sub(void*v_arg)
  {
//step_3:+sub_t
  ; SmartPtr*sp1=static_cast<SmartPtr*>(v_arg)
  ; SmartPtr sp2(*sp1)
  ; return ((void*)0)
  ;}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk