Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++: Multi threading and reference counting

Currently ive got some reference counted classes using the following:

class RefCounted
{
public:
    void IncRef()
    {
        ++refCnt;
    }
    void DecRef()
    {
        if(!--refCnt)delete this;
    }
protected:
    RefCounted():refCnt(0){}
private:
    unsigned refCnt;
    //not implemented
    RefCounted(RefCounted&);
    RefCounted& operator = (RefCounted&};
};

I also have a smart pointer class that handles reference counting , all though its not uniformly used (eg in one or two bits of performance critical code, where I minimised the number of IncRef and DecRef calls).

template<class T>class RefCountedPtr
{
public:
    RefCountedPtr(T *p)
    :p(p)
    {
        if(p)p->IncRef();
    }
    ~RefCountedPtr()
    {
        if(p)p->DecRef();
    }
    RefCountedPtr<T>& operator = (T *newP)
    {
        if(newP)newP->IncRef();
        if(p)   p   ->DecRef();
        p = newP;
        return *this;
    }
    RefCountedPtr<T>& operator = (RefCountedPtr<T> &newP)
    {
        if(newP.p)newP.p->IncRef();
        if(p)     p     ->DecRef();
        p = newP.p;
        return *this;
    }
    T& operator *()
    {
        return *p;
    }
    T* operator ->()
    {
        return p;
    }
    //comparison operators etc and some const versions of the above...
private:
    T *p;
};

For the general use of the classes themselves I plan to use a reader/writer locking system, however I dont really want to have to get a writer lock for every single IncRef and DecRef call.

I also just thought of a scenario where the pointer may be invalidated just before the IncRef call, consider:

class Texture : public RefCounted
{
public:
    //...various operations...
private:
    Texture(const std::string &file)
    {
        //...load texture from file...
        TexPool.insert(this);
    }
    virtual ~Texture()
    {
        TexPool.erase(this);
    }
    freind CreateTextureFromFile;
};
Texture *CreateTexture(const std::string &file)
{
    TexPoolIterator i = TexPool.find(file);
    if(i != TexPool.end())return *i;
    else return new Texture(file);
}
ThreadA                                ThreadB
t = CreateTexture("ball.png");
t->IncRef();
...use t...                            t2 = CreateTexture("ball.png");//returns *t
...                                    thread suspended...
t->DecRef();//deletes t                ...
...                                    t2->IncRef();//ERROR

So I guess I need to change the ref counting model entirely, the reason I added a ref after the return in the design was to support things like the following:

MyObj->GetSomething()->GetSomethingElse()->DoSomething();

rather than having to:

SomeObject a = MyObj->GetSomething();
AnotherObject *b = a->GetSomethingElse();
b->DoSomething();
b->DecRef();
a->DecRef();

Is there a clean way for fast reference counting in c++ in a multi threaded environment?

like image 637
Fire Lancer Avatar asked Jul 16 '09 14:07

Fire Lancer


3 Answers

Make the reference counting atomic and you won't need any lock. In Windows ::InterlockedIncrement and ::InterlockedDecrement can be used. In C++ 0x, you have atomic<>.

like image 65
Edouard A. Avatar answered Nov 03 '22 04:11

Edouard A.


Unless you know it's a specific bottleneck I'd just use boost::shared_ptr

It is very fast however there is a bit of extra overhead in the extra control block being allocated. On the other hand it has many benefits:

  • It is portable
  • It is correct
  • You don't have to waste your mental cycles on it leaving you time to actually get stuff done
  • It is fast
  • It is industry standard and other programmers will immediately understand it.
  • It forces you to use boost which if you aren't you should be

Also note, you probably won't want a reader\writer lock for a ref counted object. The contention is minimal and the extra overhead will completely overwhelm any benefits you would have. The shared pointer is implemented with a chip level atomic int operation, this is significantly better than a normal mutex which is significantly faster than a reader\writer lock.

like image 44
Matt Price Avatar answered Nov 03 '22 02:11

Matt Price


If you don't want to use boost or C++0X, but you still want lockless refcounting, you can do so by including the correct platform-specific atomic-increment/atomic-decrement assembly routines in your code. As an example, here's the AtomicCounter class that I use for my reference counting; it works under most common OS's:

https://public.msli.com/lcs/muscle/html/AtomicCounter_8h_source.html

Yes, it's a nasty mess of #ifdefs. But it does work.

like image 6
Jeremy Friesner Avatar answered Nov 03 '22 04:11

Jeremy Friesner