Domanda

Qualcuno sa di un'implementazione shared_ptr completamente thread-safe? Per esempio. potenziare l'implementazione di shared_ptr è thread-safe per gli obiettivi (refcounting) e sicuro anche per le letture simultanee dell'istanza shared_ptr , ma non per la scrittura o per la lettura / scrittura.

(vedi Boost docs , esempi 3, 4 e 5).

Esiste un'implementazione di shared_ptr completamente thread-safe per le istanze shared_ptr ?

Strano che promuove i documenti dice che:

  

oggetti shared_ptr offrono lo stesso livello di sicurezza dei thread dei tipi incorporati.

Ma se si confronta un normale puntatore (tipo incorporato) con smart_ptr , la scrittura simultanea di un normale puntatore è thread-safe, ma la scrittura simultanea su un smart_ptr non lo è.

EDIT: intendo un'implementazione senza blocco sull'architettura x86.

EDIT2: un caso d'uso di esempio per un puntatore così intelligente potrebbe essere un numero di thread di lavoro che aggiornano un global_ptr globale con un oggetto di lavoro corrente e un thread di monitoraggio che preleva campioni casuali degli elementi di lavoro. Shared-ptr sarebbe proprietario dell'oggetto di lavoro fino a quando non gli viene assegnato un altro puntatore di oggetto di lavoro (distruggendo così l'oggetto di lavoro precedente). Il monitor otterrebbe la proprietà dell'oggetto di lavoro (prevenendo così la distruzione dell'oggetto di lavoro) assegnandolo al proprio ptr condiviso. Può essere fatto con XCHG e la cancellazione manuale, ma sarebbe bello se un ptr condiviso potesse farlo.

Un altro esempio è in cui il global shared-ptr contiene un "processore", assegnato da un thread e utilizzato da un altro thread. Quando l'utente " utente " il thread vede che il processore shard-ptr è NULL, usa una logica alternativa per eseguire l'elaborazione. Se non è NULL, impedisce che il processore venga distrutto assegnandolo al proprio shared-ptr.

È stato utile?

Soluzione

L'aggiunta delle barriere necessarie per un'implementazione così condivisa e completamente thread-safe influirebbe probabilmente sulle prestazioni. Considera la seguente razza (nota: lo pseudocodice abbonda):

Discussione 1:     global_ptr = A;

Discussione 2:     global_ptr = B;

Discussione 3:     local_ptr = global_ptr;

Se lo suddividiamo nelle sue operazioni costitutive:

Discussione 1:

A.refcnt++;
tmp_ptr = exchange(global_ptr, A);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Discussione 2:

B.refcnt++;
tmp_ptr = exchange(global_ptr, B);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Discussione 3:

local_ptr = global_ptr;
local_ptr.refcnt++;

Chiaramente, se il thread 3 legge il puntatore dopo lo scambio di A, allora B va e lo elimina prima che il conteggio dei riferimenti possa essere incrementato, accadranno cose cattive.

Per gestire questo, abbiamo bisogno di un valore fittizio da usare mentre il thread 3 sta eseguendo l'aggiornamento refcnt: (nota: compare_exchange (variabile, previsto, nuovo) sostituisce atomicamente il valore nella variabile con nuovo se è attualmente uguale a nuovo, quindi restituisce vero se lo ha fatto correttamente)

Discussione 1:

A.refcnt++;
tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Discussione 2:

B.refcnt++;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

Discussione 3:

tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, BAD_PTR))
    tmp_ptr = global_ptr;
local_ptr = tmp_ptr;
local_ptr.refcnt++;
global_ptr = tmp_ptr;

Ora hai dovuto aggiungere un ciclo, con l'atomica nel mezzo della tua / lettura / operazione. Questa non è una buona cosa, può essere estremamente costosa su alcune CPU. Inoltre, sei anche impegnato ad aspettare. Puoi iniziare a diventare intelligente con i futex e quant'altro, ma a quel punto hai reinventato il blocco.

Questo costo, che deve essere sostenuto da ogni operazione, ed è di natura molto simile a quello che un lucchetto ti darebbe comunque, è il motivo per cui generalmente non vedi implementazioni condivise con thread_ptr. Se hai bisogno di una cosa del genere, consiglierei di includere un mutex e shared_ptr in una classe di convenienza per automatizzare il blocco.

Altri suggerimenti

La scrittura simultanea su un puntatore incorporato non è certamente sicura per i thread. Considera le implicazioni della scrittura sullo stesso valore rispetto alle barriere di memoria se vuoi davvero farti impazzire (ad esempio, potresti avere due thread pensando che lo stesso puntatore avesse valori diversi).

RE: commento - il motivo per cui i built-in non eseguono la doppia eliminazione è perché non vengono eliminati (e l'implementazione di boost :: shared_ptr che uso non raddoppierebbe, poiché utilizza uno speciale incremento atomico e decremento, quindi eliminerebbe solo una volta, ma il risultato potrebbe avere il puntatore di uno e il conteggio di riferimento dell'altro. O praticamente qualsiasi combinazione dei due. Sarebbe male.). La dichiarazione nei documenti boost è corretta così com'è, ottieni le stesse garanzie che hai con un built-in.

RE: EDIT2 - La prima situazione che stai descrivendo è molto diversa tra l'uso di built-in e shared_ptrs. In uno (XCHG ed eliminazione manuale) non esiste un conteggio dei riferimenti; stai assumendo di essere il solo ed unico proprietario quando lo fai. Se usi i puntatori condivisi, stai dicendo che altri thread potrebbero avere proprietà, il che rende le cose molto più complesse. Credo che sia possibile con un confronto e uno scambio, ma questo sarebbe molto non portatile.

C ++ 0x esce con una libreria atomica, che dovrebbe rendere molto più semplice la scrittura di codice multi-thread generico. Probabilmente dovrai aspettare fino a quando non verrà fuori per vedere buone implementazioni di riferimento multipiattaforma di puntatori intelligenti thread-safe.

Non conosco un'implementazione del puntatore così intelligente, anche se devo chiedere: come può essere utile questo comportamento? Gli unici scenari a cui riesco a pensare dove potresti trovare aggiornamenti puntatori simultanei sono le condizioni di gara (ovvero i bug).

Questa non è una critica - potrebbe esserci un caso d'uso legittimo, non riesco proprio a pensarci. Per favore fatemi sapere!

Ri: EDIT2 Grazie per aver fornito un paio di scenari. Sembra che le scritture di puntatori atomici sarebbero utili in quelle situazioni. (Una piccola cosa: per il secondo esempio, quando hai scritto " Se non è NULL, impedisce al processore di essere distrutto assegnandolo al proprio ptr condiviso " ;, spero che tu intendessi assegnare il puntatore condiviso globale a il puntatore condiviso locale prima poi verifica se il puntatore condiviso locale è NULL - il modo in cui lo hai descritto è soggetto a una condizione di competizione in cui il puntatore condiviso globale diventa NULL dopo averlo testato e prima di assegnare a quello locale.)

Puoi utilizzare questa implementazione Puntatori per il conteggio dei riferimenti atomici almeno per implementare il meccanismo di conteggio dei riferimenti.

Il compilatore potrebbe già fornire i puntatori intelligenti thread-safe nei nuovi standard C ++. Credo che TBB sta pianificando di aggiungere un puntatore intelligente, ma non credo che sia stato ancora incluso. Tuttavia, potresti essere in grado di utilizzare uno dei contenitori thread-safe di TBB.

Puoi farlo facilmente includendo un oggetto mutex con ogni puntatore condiviso e avvolgendo i comandi di incremento / decremento con il blocco.

Non penso che sia così facile, non è sufficiente avvolgere le tue classi sh_ptr con un CS. È vero che se si mantiene un singolo CS per tutti i puntatori condivisi, si può evitare l'accesso reciproco e la cancellazione di oggetti sh_ptr tra thread diversi. Ma questo sarebbe terribile, un oggetto CS per ogni puntatore condiviso sarebbe un vero collo di bottiglia. Sarebbe adatto se ogni nuovo ptr -s avvolgibile avesse CS diversi, ma in questo modo dovremmo creare il nostro CS dinamicamente e garantire i copiatori delle classi sh_ptr per trasmettere questo C condiviso. Ora siamo arrivati ??allo stesso problema: chi garantisce che questo Cs ptr è già stato eliminato o meno. Possiamo essere un po 'più intelligenti con i volatile m_b rilasci rilasciati per istanza, ma in questo modo non possiamo bloccare le lacune di sicurezza tra il controllo del flag e l'uso dei C condivisi. Non riesco a vedere una risoluzione completamente sicura per questo problema. Forse quel terribile C globale sarebbe il minimo male come uccidere l'app. (scusate il mio inglese)

Questo potrebbe non essere esattamente quello che vuoi, ma la documentazione boost :: atomic fornisce un esempio su come usare un contatore atomico con intrusive_ptr . intrusive_ptr è uno dei puntatori intelligenti Boost, fa "conteggio dei riferimenti intrusivi", il che significa che il contatore è "incorporato" nell'obiettivo anziché fornire dal puntatore intelligente.

Potenzia atomic Esempi di utilizzo:

http://www.boost.org/doc/html/atomic/ usage_examples.html

Secondo me, la soluzione più semplice è usare un intrusive_ptr con alcune modifiche minori (ma necessarie).

Ho condiviso la mia implementazione di seguito:

http://www.philten.com/boost-smartptr-mt/

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