2012-04-22 14 views
18

Sto scrivendo un puntatore condiviso intrusivo e sto utilizzando le strutture C++ 11 <atomic> per il contatore di riferimento. Qui ci sono i frammenti pertinenti del mio codice:C++ 11 atomica e conteggio di riferimento del puntatore condiviso intrusivo

//... 
mutable std::atomic<unsigned> count; 
//... 

void 
SharedObject::addReference() const 
{ 
    std::atomic_fetch_add_explicit (&count, 1u, 
     std::memory_order_consume); 
} 

void 
SharedObject::removeReference() const 
{ 
    bool destroy; 

    destroy = std::atomic_fetch_sub_explicit (&count, 1u, 
     std::memory_order_consume) == 1; 

    if (destroy) 
     delete this; 
} 

ho iniziato con memory_order_acquire e memory_order_release primo momento, ma poi mi sono convinto che memory_order_consume dovrebbe essere abbastanza buono. Dopo un'ulteriore riflessione, mi sembra che anche memory_order_relaxed dovrebbe funzionare.

Ora, la domanda è se io possa usare memory_order_consume per le operazioni o potrei usare un ordine più debole (memory_order_relaxed) o dovrei usare un ordine più severo?

+0

Poiché il contatore agisce essenzialmente come un blocco ricorsivo per l'istruzione 'delete', direi che" acquisisci "in" addReference "e" release "in" removeReference "sono gli ordinamenti giusti. Ma il tuo 'addReference' dovrebbe anche assicurarsi che il contatore non fosse zero! –

+0

@KerrekSB: il contatore può essere zero in 'addReference()' dopo che l'oggetto è stato creato prima di essere assegnato a 'SharedPtr <>'. Acquisire/rilasciare semantica sembra che dovrebbe sempre funzionare. Ma non è possibile utilizzare un vincolo di ordine più debole e perché no? – wilx

+0

Informazioni sullo zero: Supponiamo che il Refcount sia 1. Ora il thread 1 vuole eliminare l'oggetto e le chiamate sottrarre. Se a questo punto il thread 2 vuole * aumentare * il numero di thread, incrementa zero a uno, ma il thread 1 andrà avanti e cancellerà comunque l'oggetto. Questo dovrebbe essere evitato. –

risposta

20
void 
SharedObject::addReference() const 
{ 
    std::atomic_fetch_add_explicit (&count, 1u, std::memory_order_relaxed); 
} 

void 
SharedObject::removeReference() const 
{ 
    if (std::atomic_fetch_sub_explicit (&count, 1u, std::memory_order_release) == 1) { 
     std::atomic_thread_fence(boost::memory_order_acquire); 
     delete this; 
    } 
} 

si desidera utilizzare atomic_thread_fence tale che il delete è strettamente dopo la fetch_sub. Reference

Citazione dal testo collegato:

Aumentare il contatore di riferimento può sempre essere fatto con memory_order_relaxed: Nuovi riferimenti a un oggetto possono essere formati solo da un riferimento esistente, e passando un riferimento esistente da un thread a un altro deve già fornire la sincronizzazione richiesta.

È importante rispettare ogni possibile accesso all'oggetto in uno filo (attraverso un riferimento esistente) accadere prima di eliminare l'oggetto in un thread diverso. Ciò si ottiene eseguendo un'operazione di "rilascio" dopo aver rilasciato un riferimento (qualsiasi accesso all'oggetto tramite questo riferimento deve ovviamente verificarsi prima) e un'operazione "acquisisci" prima di eliminare l'oggetto.

Sarebbe possibile utilizzare memory_order_acq_rel per l'operazione fetch_sub , ma ciò comporta inutili operazioni di "acquisire" quando il contatore riferimento non ha ancora raggiunto il valore zero e può imporre una penalizzazione delle prestazioni .

+0

Mentre ho già accettato la risposta e implementato il mio puntatore intrusivo in quel modo, la citazione mi ha fatto riflettere un po 'e qui c'è un'altra domanda. Che ne dite di rilassare il decremento di 'memory_order_relaxed' e fare il vero ramo di' if() 'usa' memory_order_acq_rel'? – wilx

+0

con '_relaxed', quindi' _acq_rel', non esiste un ordinamento rigoroso di 'fetch_sub (_relaxed)' e 'fence (_acq_rel)' quindi 'delete'. Potresti essere interessato a questa [pagina] (http://www.chaoticmind.net/~hcb/projects/boost.atomic/doc/atomic/thread_coordination.html). – user2k5

+0

Ok, dopo aver letto la pagina penso di capire che non può essere 'fetch_sub (_relaxed)'. È perché qualsiasi operazione '_relaxed' non è ordinata contro altre operazioni. Ma per quanto riguarda 'fetch_sub (_consume)' e 'fence (_acq_rel)'? Lì mi sembra che 'fetch_sub (_consume)' pone una barriera di riordino del compilatore e 'fence (_acq_rel)' ordina tutti i carichi e gli archivi contro il resto della macchina e il seguente 'delete' avrà una visione coerente della memoria. O mi sbaglio? – wilx

Problemi correlati