Domanda

Come si implementa un sistema di conteggio dei riferimenti efficiente e thread-safe su CPU X86 nel linguaggio di programmazione C++?

Mi imbatto sempre nel problema che il operazioni critiche non atomiche, e le operazioni di interblocco X86 disponibili non sono sufficienti per implementare il sistema di conteggio dei riferimenti.

Il seguente articolo tratta questo argomento, ma richiede istruzioni speciali per la CPU:

http://www.ddj.com/architect/184401888

È stato utile?

Soluzione

Al giorno d'oggi, puoi utilizzare il puntatore intelligente Boost/TR1 shared_ptr<> per mantenere i riferimenti contati.

Funziona alla grande;niente confusione, niente confusione.La classe shared_ptr<> si occupa di tutto il blocco necessario sul refcount.

Altri suggerimenti

In VC++, puoi usare _InterlockedCompareExchange.

do
   read the count
   perform mathematical operation
   interlockedcompareexchange( destination, updated count, old count)
until the interlockedcompareexchange returns the success code.

Su altre piattaforme/compilatori, utilizzare l'intrinseco appropriato per l'istruzione LOCK CMPXCHG esposta da _InterlockedCompareExchange di MS.

A rigor di termini, dovrai attendere fino al C++0x per poter scrivere codice thread-safe in puro C++.

Per ora, puoi utilizzare Posix o creare i tuoi wrapper indipendenti dalla piattaforma per il confronto e lo scambio e/o l'incremento/decremento interbloccato.

Win32 InterlockedIncrementAcquire e InterlockedDecrementRelease (se vuoi essere sicuro e preoccuparti delle piattaforme con possibile riordino, quindi devi emettere barriere di memoria allo stesso tempo) o InterlockedIncrement e InterlockedDecrement (se sei sicuro che rimarrai x86), sono atomici e lo faranno Fai il lavoro.

Detto questo, Boost/TR1 shared_ptr<> gestirà tutto questo per te, quindi, a meno che tu non abbia bisogno di implementarlo da solo, probabilmente farai del tuo meglio per attenervisi.

Tieni presente che il blocco è molto costoso e si verifica ogni volta che passi gli oggetti tra i puntatori intelligenti, anche quando l'oggetto è attualmente di proprietà di un thread (la libreria dei puntatori intelligenti non lo sa).

Detto questo, potrebbe esserci una regola pratica applicabile qui (sono felice di essere corretto!)

Se quanto segue si applica al tuo caso:

  • Hai strutture di dati complesse per le quali sarebbe difficile scrivere distruttori (o dove la semantica dei valori in stile STL sarebbe inappropriata, in base alla progettazione), quindi hai bisogno di puntatori intelligenti per farlo per te, e
  • Stai utilizzando più thread che condividono questi oggetti e
  • Ti preoccupi delle prestazioni così come della correttezza

...allora l'effettiva raccolta dei rifiuti potrebbe essere una scelta migliore.Sebbene GC abbia una cattiva reputazione in termini di prestazioni, è tutto relativo.Credo che si confronti molto favorevolmente con il blocco dei puntatori intelligenti.Questa è stata una parte importante del motivo per cui il team CLR ha scelto il vero GC invece di qualcosa che utilizzava il conteggio dei riferimenti.Vedere Questo articolo, in particolare questo confronto netto di cosa significhi l'assegnazione di referenze se si stanno contando:

nessun conteggio dei riferimenti:

a = b;

conteggio dei riferimenti:

if (a != null)
    if (InterlockedDecrement(ref a.m_ref) == 0)
            a.FinalRelease();

if (b != null)
    InterlockedIncrement(ref b.m_ref);

a = b;

Se l'istruzione stessa non è atomica, è necessario rendere critica la sezione di codice che aggiorna la variabile appropriata.

cioè. È necessario impedire ad altri thread di accedere a quella sezione di codice utilizzando uno schema di blocco.Ovviamente i blocchi devono essere atomici, ma puoi trovare un meccanismo di blocco atomico all'interno della classe pthread_mutex.

La questione dell’efficienza:La libreria pthread è quanto più efficiente possibile e garantisce comunque che il blocco mutex sia atomico per il tuo sistema operativo.

È costoso:Probabilmente.Ma tutto ciò che richiede una garanzia ha un costo.

Quel particolare codice pubblicato nell'articolo ddj aggiunge ulteriore complessità per tenere conto dei bug nell'utilizzo dei puntatori intelligenti.

Nello specifico, se non puoi garantire che il puntatore intelligente non cambierà in un'assegnazione a un altro puntatore intelligente, stai sbagliando o stai facendo qualcosa di molto inaffidabile fin dall'inizio.Se il puntatore intelligente può cambiare mentre viene assegnato a un altro puntatore intelligente, ciò significa che il codice che esegue l'assegnazione non possiede il puntatore intelligente, il che è sospetto fin dall'inizio.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top