2009-07-16 18 views
6

Attualmente Ive ha ottenuto alcune classi di riferimento contato con la seguente:C++: Multi threading e riferimento contando

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

Ho anche una classe di puntatore intelligente che gestisce il conteggio di riferimento, tutti però la sua non è utilizzato in modo uniforme (ad esempio in uno o due bit di codice critico delle prestazioni, in cui ho ridotto al minimo il numero di chiamate IncRef e 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; 
}; 

Per l'uso generale delle classi stesse che prevede di utilizzare un sistema di lettura/scrittura di blocco, ma realmente non vogliono avere a ottenere un blocco scrittore per ogni singola chiamata IncRef e DecRef.

ho anche pensato di uno scenario in cui il puntatore può essere invalidata solo prima della chiamata IncRef, considerare:

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 

quindi credo che ho bisogno di cambiare il modello di conteggio ref del tutto, la ragione ho aggiunto un ref dopo il ritorno nella progettazione è stato quello di sostenere le cose come la seguente:

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

piuttosto che dover:

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

Esiste un modo pulito per il conteggio dei riferimenti rapidi in C++ in un ambiente con più thread?

+9

Usate 'boost :: shared_ptr': http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr. htm – avakar

+1

Che è disponibile come std :: tr1 :: shared_ptr anche in alcuni compilatori – bdonlan

risposta

16

Effettuare il conteggio di riferimento atomico e non sarà necessario alcun blocco. In Windows :: InterlockedIncrement e :: InterlockedDecrement possono essere utilizzati. In C++ 0x, hai atomico <>.

+0

In caso contrario, inserire sezioni critiche attorno alle potenziali modifiche. – Goz

+3

InterlockedIncrement è molto più veloce delle sezioni critiche ed è disponibile nell'API win32. –

+2

Questo non aggiusta la gara; l'ultimo riferimento potrebbe essere rimosso tra CreateTexture e AddRef con l'API fornita. – bdonlan

2

osg, OpenSceneGraph ha una tale struttura.

si derivano le classi da osg :: Riferimenti e non ti interessa distruttore anche in multithread.

basta creare classi:

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

invece di:

MyClass* m = new MyClass(); 
10

Se non si sa che è un collo di bottiglia specifica mi basta usare boost::shared_ptr

è molto veloce tuttavia ci è un po 'di overhead extra nel blocco di controllo aggiuntivo assegnato. D'altra parte ha molti vantaggi:

  • è portatile
  • È corretto
  • Non dovete sprecare i vostri cicli mentali su di esso si lascia il tempo di effettivamente ottenere cose fatto
  • È veloce
  • È lo standard del settore e altri programmatori lo capiranno immediatamente.
  • ti costringe a usare boost che, se non siete dovreste essere

Si noti inoltre, probabilmente non si vuole un blocco scrittore lettore \ per un oggetto rif contati. La contesa è minima e l'overhead aggiuntivo supererà completamente tutti i benefici che avresti. Il puntatore condiviso è implementato con un funzionamento int atomico a livello di chip, questo è significativamente migliore di un mutex normale che è significativamente più veloce di un blocco reader \ writer.

+0

boost :: shared_ptr ha anche un'implementazione senza blocco che lo rende una buona scelta per le applicazioni multithread, a meno che non sia costruito con BOOST_SP_NO_ATOMIC_ACCESS –

+0

Ho detto esplicitamente che non volevo un blocco per il conteggio dei riferimenti perché questo è un sacco di blocco di uno sblocco anche quando si passano dati, stavo parlando di oggetti che hanno il loro contenuto manipolato su più thread :) –

+0

Come boost :: shared_ptr corregge il problema della gara? E 'ancora qualcosa che accade dopo che l'oggetto è stato creato, e non voglio che il pool mantenga un riferimento dato che ho bisogno di cancellare la texture non attualmente in uso, quindi c'è ancora un punto tra una texture memorizzata nella cache e una nuova riferimento creato, in cui potrebbe essere distrutto? –

1

boost :: shared_ptr e Poco :: SharedPtr entrambi avvolgono questo idioma in un puntatore intelligente indipendente.

Se si desidera il conteggio dei riferimenti intrusivi, come dimostrato sopra, l'AutoPtr di Poco è una buona implementazione funzionante.

EDIT: avrei aggiunto collegamenti, ma ero troppo basso sulla reputazione. Google per qualsiasi nome di classe e dovresti trovare la tua strada.

1

Il tuo problema principale è che non si acquisisce un riferimento prima del ritorno di CreateTexture. Se stai open-codifica in questo modo, il modo più semplice di gestire la cosa è di avere un lock attorno TexPool che viene ripreso anche quando si rilascia i riferimenti prima della cancellazione, in questo modo:

// 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(); 
    } 
} 

e:

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

Detto questo, come altri hanno accennato, boost :: shared_ptr (aka std :: tr1 :: shared_ptr) implementa tutto questo in un modo sicuro e privo di blocco, e ha anche il supporto per i puntatori deboli, che aiuteranno nella cache delle texture .

6

Se non si desidera utilizzare boost o C++ 0X, ma si desidera comunque il conteggio senza blocchi, è possibile farlo includendo le routine di assemblaggio di incremento/decremento atomico specifiche della piattaforma nel codice. Ad esempio, ecco la classe AtomicCounter che utilizzo per il conteggio dei riferimenti; funziona sotto più comuni sistemi operativi:

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

Sì, si tratta di un brutto pasticcio di #ifdefs. Ma funziona.

0

Penso che tu abbia bisogno di sezioni critiche per questo particolare progetto. Un posto in cui è richiesto è CreateTexture, perché altrimenti si corre il rischio di avere più di un oggetto texture identico nel sistema. E, in generale, se diversi thread possono creare e distruggere la stessa texture, diventa "mutable shared state".

2

Volevate thread-safe o atomicamente thread-safe? boot :: shared_ptr è semplicemente thread-safe. Devi comunque "possedere" un shared_ptr per poterlo copiare in modo sicuro.

Ci sono alcune cose sperimentali che ho fatto sul conteggio dei riferimenti atomicamente sicuro ai thread qui allo http://atomic-ptr-plus.sourceforge.net/ che può darti un'idea di cosa è coinvolto.

+0

Questo è un esempio eccellente. Vedi anche http://www.drdobbs.com/cpp/184401888;jsessionid=S3LQ44DGLYBR1QE1GHPSKHWATMY32JVN?_requestid=4096 per qualche panoramica astratta che cosa sta succedendo. – mtasic85

0

Date un'occhiata a questo pdf: http://www.research.ibm.com/people/d/dfb/papers/Bacon01Concurrent.pdf

questo descrive un sistema di conteggio di riferimento, che non ha bisogno di alcun blocco. (Bene, è necessario "mettere in pausa" i thread uno alla volta che può contare come blocchi.) Raccoglie anche i cicli di spazzatura. Lo svantaggio è che è molto più complesso.Ci sono anche alcune cose importanti rimaste come esercizio per il lettore. Come succede quando viene creato un nuovo thread o eliminato uno vecchio o come trattare oggetti intrinsecamente aciclici. (Se decidete di farlo, fatemi sapere come li avete fatti scorrere.)