C ++: многопоточность и подсчет ссылок - PullRequest
6 голосов
/ 16 июля 2009

В настоящее время я получил несколько классов с подсчетом ссылок, используя следующее:

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&};
};

У меня также есть класс интеллектуальных указателей, который обрабатывает подсчет ссылок, хотя он используется неравномерно (например, в одном или двух битах кода, критичного к производительности, где я минимизировал количество вызовов IncRef и DecRef).

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;
};

Для общего использования самих классов я планирую использовать систему блокировки чтения / записи, однако я действительно не хочу получать блокировку записи для каждого отдельного вызова IncRef и DecRef.

Я также только что подумал о сценарии, в котором указатель может быть признан недействительным непосредственно перед вызовом IncRef, рассмотрим:

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

Так что я думаю, что мне нужно полностью изменить модель подсчета ссылок, поэтому я добавил ссылку после возврата в дизайн, чтобы поддерживать такие вещи, как:

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

вместо того, чтобы:

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

Существует ли чистый способ быстрого подсчета ссылок в c ++ в многопоточной среде?

Ответы [ 10 ]

16 голосов
/ 16 июля 2009

Сделайте подсчет ссылок атомарным, и вам не понадобится блокировка. В Windows :: InterlockedIncrement и :: InterlockedDecrement могут быть использованы. В C ++ 0x у вас есть атомарный <>.

10 голосов
/ 16 июля 2009

Если вы не знаете, что это конкретное узкое место, я бы просто использовал boost::shared_ptr

Это очень быстро, однако в выделенном дополнительном блоке управления есть некоторые дополнительные издержки. С другой стороны, у него много преимуществ:

  • Это портативный
  • Это правильно
  • Вам не нужно тратить на это свои ментальные циклы, оставляя время, чтобы на самом деле все было сделано
  • Это быстро
  • Это отраслевой стандарт, и другие программисты сразу поймут его.
  • Это заставляет вас использовать boost, который, если вы не должны быть

Также обратите внимание, что вам, вероятно, не понадобится блокировка чтения / записи для объекта с подсчетом ссылок. Разногласия минимальны, и дополнительные издержки полностью превзойдут все ваши преимущества. Общий указатель реализован с помощью атомарной операции на уровне чипа, это значительно лучше, чем обычный мьютекс, который значительно быстрее, чем блокировка чтения / записи.

6 голосов
/ 16 июля 2009

Если вы не хотите использовать boost или C ++ 0X, но вам все еще нужен пересчет без блокировки, вы можете сделать это, включив в свой код правильные процедуры сборки для атомарного приращения / атомарного декремента для конкретной платформы. Например, вот класс AtomicCounter, который я использую для подсчета ссылок; он работает под наиболее распространенными ОС:

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

Да, это неприятный беспорядок #ifdefs. Но это работает.

2 голосов
/ 17 июля 2009

Вы хотели поточнобезопасный или атомарно поточнобезопасный? boot :: shared_ptr является просто потокобезопасным. Вам все еще нужно «владеть» shared_ptr, чтобы безопасно его скопировать.

Есть несколько экспериментальных вещей, которые я сделал для атомно-потокового подсчета ссылок здесь http://atomic -ptr-plus.sourceforge.net / , которая может дать вам представление о том, что происходит.

2 голосов
/ 16 июля 2009

osg, OpenSceneGraph имеет такую ​​структуру.

вы извлекаете свои классы из osg :: Referenced и вам не безразличен деструктор даже в многопоточности.

вы просто создаете классы как:

osg::ref_ptr<MyClass> m = new MyClass();

вместо:

MyClass* m = new MyClass();
1 голос
/ 17 июля 2009

Ваш кеш должен использовать boost::weak_ptr или аналогичную конструкцию.

1 голос
/ 16 июля 2009

Ваша основная проблема в том, что вы не получаете ссылку до того, как CreateTexture вернется. Если вы работаете с открытым кодом, как это, самый простой способ справиться с этим - это иметь блокировку вокруг TexPool, которая также используется при освобождении ссылок перед удалением, например:

// PSEUDOCODE WARNING: --refcnt MUST be replaced by an atomic decrement-and-test
// Likewise, AddRef() MUST use an atomic increment.
void DecRef() {
    if (!--refcnt) {
        lock();
        if (!refcnt)
            delete this;
        unlock();
    }
}

и

Texture *CreateTexture(const std::string &file)
{
    lock();

    TexPoolIterator i = TexPool.find(file);
    if(i != TexPool.end()) {
        *i->AddRef();
        unlock();
        return *i;
    }
    unlock();
    return new Texture(file);
}

Тем не менее, как уже упоминали другие, boost :: shared_ptr (он же std :: tr1 :: shared_ptr) реализует все это без блокировки и безопасным способом, а также имеет поддержку слабых указателей, которые помогут с вашим кешем текстур .

1 голос
/ 16 июля 2009

boost :: shared_ptr и Poco :: SharedPtr оборачивают эту идиому в автономный интеллектуальный указатель.

Если вы хотите навязчивого подсчета ссылок, как вы продемонстрировали выше, AutoPtr Poco - это хорошая, работающая реализация.

РЕДАКТИРОВАТЬ: Я бы добавил ссылки, но у меня была слишком низкая репутация. Google для любого из имен классов, и вы должны найти свой путь.

0 голосов
/ 21 июля 2009

Взгляните на этот pdf: http://www.research.ibm.com/people/d/dfb/papers/Bacon01Concurrent.pdf

Здесь описывается система подсчета ссылок, которая не нуждается в блокировке. (Ну, вам нужно «приостановить» потоки по одному за раз, что может считаться блокировкой.) Он также собирает циклы мусора. Недостаток в том, что это намного сложнее. Есть также некоторые важные вещи, оставленные читателю в качестве упражнения. Например, что происходит, когда создается новый поток или удаляется старый, или как работать с ациклическими объектами. (Если вы решите сделать это, пожалуйста, дайте мне знать, как вы это сделали.)

0 голосов
/ 17 июля 2009

Я думаю, вам нужны критические разделы для этого конкретного дизайна. Одним из мест, где это требуется, является CreateTexture, потому что в противном случае вы рискуете иметь более одного идентичного объекта текстуры в системе. И вообще, если несколько потоков могут создавать и уничтожать одну и ту же текстуру, это делает ее «изменяемым общим состоянием».

...