Domanda

non riuscivo a trovare informazioni sufficienti sui tipi ConcurrentDictionary, così ho pensato di chiedere a questo proposito qui.

Attualmente, uso un Dictionary per contenere tutti gli utenti che si accede costantemente da più fili (da un pool di thread, quindi nessuna quantità esatta di fili), ed ha sincronizzato accesso.

Di recente ho scoperto che c'era un insieme di collezioni thread-safe in .NET 4.0, e sembra essere molto piacevole. Mi chiedevo, quale sarebbe il 'più efficiente e più facile da gestire' opzione, come ho la possibilità tra l'avere un Dictionary normale con accesso sincronizzato, o avere una ConcurrentDictionary che è già thread-safe.

riferimento al NET 4.0 di ConcurrentDictionary

È stato utile?

Soluzione

Una raccolta threadsafe contro un non-threadsafe-collezione può essere considerata in modo diverso.

Si consideri un negozio senza impiegato, tranne che alla cassa. Hai un sacco di problemi se la gente non agire in modo responsabile. Per esempio, diciamo che un cliente prende una lattina da una piramide-lattina, mentre un impiegato sta costruendo la piramide, l'inferno avrebbe rotto sciolto. Oppure, cosa succede se due clienti raggiunge per la stessa voce, allo stesso tempo, chi vince? Ci sarà una lotta? Questo è un non-threadsafe-raccolta. C'è un sacco di modi per evitare problemi, ma tutti richiedono un qualche tipo di blocco, o l'accesso piuttosto esplicito in un modo o nell'altro.

D'altra parte, si consideri un negozio con un impiegato ad una scrivania, e si può acquistare solo attraverso di lui. Si ottiene in linea, e gli chiedi di un articolo, che porta di nuovo a voi, e si va fuori dalla linea. Se avete bisogno di più elementi, è possibile scegliere solo come molti articoli su ogni andata e ritorno, come si può ricordare, ma è necessario fare attenzione per evitare di monopolizzare l'impiegato, questa sarà la rabbia degli altri clienti in fila dietro di voi.

Ora considerare questo. Nel negozio con un addetto, che cosa succede se si ottiene tutta la strada verso la parte anteriore della linea, e chiedere l'impiegato "Avete qualche carta igienica", e lui dice "sì", e poi si va "Ok, I' ll tornare a voi quando so quanto ho bisogno", quindi per il momento sei di nuovo alla parte anteriore della linea, il negozio può naturalmente essere esaurito. Questo scenario non è impedito da una collezione threadsafe.

A threadsafe raccolta garantisce che le sue strutture dati interne sono validi in ogni momento, anche se accessibile da più thread.

Una collezione non threadsafe non viene fornito con tali garanzie. Per esempio, se si aggiunge qualcosa a un albero binario su un thread, mentre un altro thread è occupato riequilibrare l'albero, non c'è alcuna garanzia verrà aggiunta la voce, o addirittura che l'albero è ancora valido dopo, potrebbe essere corrotto oltre ogni speranza.

Una threadsafe raccolta non, tuttavia, garantisce che le operazioni sequenziali sul filo tutti i lavori sullo stesso "istantanea" della sua struttura interna dei dati, il che significa che se si dispone di codice in questo modo:

if (tree.Count > 0)
    Debug.WriteLine(tree.First().ToString());

si potrebbe ottenere un NullReferenceException perché tree.Count inbetween e tree.First(), un altro thread ha sgomberato i restanti nodi dell'albero, il che significa First() torneranno null.

Per questo scenario, vi sia bisogno di vedere se la raccolta in questione ha un modo sicuro per ottenere ciò che si vuole, forse avete bisogno di riscrivere il codice di cui sopra, o potrebbe essere necessario bloccare.

Altri suggerimenti

Hai ancora bisogno di essere molto attenti quando si usano le collezioni thread-safe perché thread-safe non significa che si può ignorare tutti i problemi di threading. Quando una collezione si annuncia come thread-safe, in genere significa che rimane in uno stato costante anche quando più thread lettura e scrittura simultaneamente. Ma questo non significa che un singolo thread vedrà una sequenza di "logica" dei risultati, se si chiama più metodi.

Ad esempio, se si controlla prima se esistente e poi ottenere il valore corrispondente alla chiave, la chiave non può più esistere anche con una versione ConcurrentDictionary (perché un altro thread potrebbe avere rimosso la chiave). Hai ancora bisogno di utilizzare il blocco in questo caso (o meglio: coniugare le due chiamate utilizzando TryGetValue ).

Così li uso, ma non credo che ti dà un pass gratuito per ignorare tutti i problemi di concorrenza. Hai ancora bisogno di stare attenti.

Internamente ConcurrentDictionary utilizza un blocco separato per ciascun segmento di hash. Finché si usa solo Aggiungi / TryGetValue ei metodi come che lavorano su singole voci, il dizionario funzionerà come una struttura di dati quasi senza blocchi con il rispettivo miglioramento delle prestazioni dolce. OTOH i metodi di enumerazione (compresa la proprietà Count) bloccare tutti i secchi in una sola volta e sono quindi peggio di un Dizionario sincronizzato, prestazioni-saggio.

direi, basta usare ConcurrentDictionary.

Credo che il metodo ConcurrentDictionary.GetOrAdd è esattamente ciò che la maggior parte degli scenari multi-threaded bisogno.

Hai visto il reattiva estensioni per .Net 3.5SP1. Secondo Jon Skeet, hanno backported un fascio di estensioni parallele e strutture di dati simultanee per .Net3.5 sp1.

C'è una serie di campioni per .Net 4 Beta 2, che descrive in buona dettagli su come usarli le estensioni parallele.

Ho appena trascorso l'ultima settimana testare il ConcurrentDictionary con 32 thread per eseguire I / O. Sembra funzionare come pubblicizzato, che indicherebbe una enorme quantità di test è stato messo in esso.

Modifica :. NET 4 ConcurrentDictionary e modelli

Microsoft ha rilasciato un PDF denominato Patterns of Parallel Programming. La sua è idoneo ad accogliere vale la pena scaricare come descritto in dettaglio davvero bello i modelli giusti da utilizzare per .Net 4 estensioni concorrente e gli schemi anti-evitare. Eccolo.

In sostanza si vuole andare con il nuovo ConcurrentDictionary. A destra, fuori dalla scatola si deve scrivere meno codice per rendere i programmi thread-safe.

Abbiamo utilizzato per la raccolta ConcurrentDictionary cache, che è ri-popolata ogni 1 ora e poi leggere da più thread client, simile a la soluzione per il è questo esempio thread-safe? domanda .

Abbiamo trovato, che cambiando a ReadOnlyDictionary migliorate le prestazioni generali .

Il ConcurrentDictionary è una grande opzione se soddisfa tutte le le vostre esigenze thread-sicurezza. Se non lo è, in altre parole di voi stanno facendo nulla un po 'complesso, un Dictionary normale + lock possono essere una scelta migliore. Per esempio supponiamo che si sta aggiungendo alcuni ordini in un dizionario, e si desidera mantenere aggiornato l'importo totale degli ordini. Si può scrivere codice come questo:

private ConcurrentDictionary<int, Order> _dict;
private Decimal _totalAmount = 0;

while (await enumerator.MoveNextAsync())
{
    Order order = enumerator.Current;
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

Questo codice non è thread-safe. Più thread di aggiornamento del campo _totalAmount possono lasciare in uno stato danneggiato. Quindi, si può tentare di proteggerlo con una lock:

_dict.TryAdd(order.Id, order);
lock (_locker) _totalAmount += order.Amount;

Questo codice è "più sicuro", ma ancora non è thread-safe. Non v'è alcuna garanzia che il _totalAmount è coerente con le voci del dizionario. Altro thread può tentare di leggere questi valori, per aggiornare un elemento dell'interfaccia utente:

Decimal totalAmount;
lock (_locker) totalAmount = _totalAmount;
UpdateUI(_dict.Count, totalAmount);

La totalAmount potrebbe non corrispondere al conteggio degli ordini nel dizionario. Le statistiche visualizzate potrebbe essere sbagliato. A questo punto vi renderete conto che è necessario estendere la protezione lock per includere l'aggiornamento del dizionario:

// Thread A
lock (_locker)
{
    _dict.TryAdd(order.Id, order);
    _totalAmount += order.Amount;
}

// Thread B
int ordersCount;
Decimal totalAmount;
lock (_locker)
{
    ordersCount = _dict.Count;
    totalAmount = _totalAmount;
}
UpdateUI(ordersCount, totalAmount);

Questo codice è perfettamente sicuro, ma tutti i vantaggi di utilizzare un ConcurrentDictionary se ne sono andati.

  1. Il rendimento è peggiorata rispetto all'utilizzo di un Dictionary normale, perché il bloccaggio interno all'interno del ConcurrentDictionary è ora dispendioso e ridondante.
  2. È necessario rivedere tutto il codice per usi non protetti delle variabili condivise.
  3. Si sono bloccati con l'utilizzo di un'API imbarazzante (TryAdd ?, AddOrUpdate?).

Quindi il mio consiglio è: iniziare con un Dictionary + lock, e mantenere la possibilità di aggiornare in seguito ad un ConcurrentDictionary come ottimizzazione delle prestazioni, se questa opzione è in realtà praticabile. In molti casi non sarà.

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