Utilizzo corretto del dizionario simultaneo


84

Ho ragione nel pensare che questo sia l'uso corretto di un dizionario concorrente

private ConcurrentDictionary<int,long> myDic = new ConcurrentDictionary<int,long>();

//Main thread at program startup

for(int i = 0; i < 4; i++)
{
  myDic.Add(i, 0);
}

//Seperate threads use this to update a value

myDic[InputID] = newLongValue;

Non ho blocchi ecc. E sto solo aggiornando il valore nel dizionario anche se più thread potrebbero provare a fare lo stesso.


2
Dipende - newLongValuedipende dal valore precedente di myDic[InputID]?
Damien_The_Unbeliever

3
evitare di accedere direttamente dalla chiave myDic[InputID]per race condition. Dovresti provareGetOrAdd
Olivier Albertini

3
@OlivierAlbertini, non credo myDic[InputID]causi alcun problema quando viene utilizzato come lvalue. GetOrAddnon è una sostituzione corretta poiché aggiunge solo se il valore non esiste. Possiamo invece usare AddOrUpdateper aggiungere / aggiornare lo stesso valore nel dizionario.
Jatin Sanghvi

Risposte:


75

Dipende da cosa intendi per thread-safe.

Da MSDN - Procedura: aggiungere e rimuovere elementi da un ConcurrentDictionary :

ConcurrentDictionary<TKey, TValue>è progettato per scenari multithread. Non è necessario utilizzare blocchi nel codice per aggiungere o rimuovere elementi dalla raccolta. Tuttavia, è sempre possibile che un thread recuperi un valore e un altro thread aggiorni immediatamente la raccolta assegnando alla stessa chiave un nuovo valore.

Quindi, è possibile ottenere una visualizzazione incoerente del valore di un elemento nel dizionario.


2
Questo è un punto interessante! Useresti ancora un lucchetto in quello scenario?
Jon

@ Jon - Dipende dalla tua applicazione e se va bene lo farai. Ma direi che se desideri visualizzazioni coerenti degli elementi, dovresti racchiudere ogni lettura e aggiornamento di un elemento in un lucchetto.
Oded

12
Penso che questo non sia quello che dice il dottore. L'incoerenza ha a che fare con ciò che la vista contiene, se la vista è solo il valore, allora è perfettamente coerente. Finché ottieni il valore di una chiave, il valore della chiave nel dizionario potrebbe cambiare. Questo è incoerente quanto il valore DateTime.Now.
George Mavritsakis

4

Il modo migliore per scoprirlo è controllare la documentazione MSDN.

Per ConcurrentDictionary la pagina è http://msdn.microsoft.com/en-us/library/dd287191.aspx

Nella sezione sulla sicurezza dei thread, è indicato "Tutti i membri pubblici e protetti di ConcurrentDictionary (Of TKey, TValue) sono thread-safe e possono essere utilizzati contemporaneamente da più thread".

Quindi dal punto di vista della concorrenza stai bene.


2

Si hai ragione.

Questo e la possibilità di enumerare il dizionario su un thread mentre lo si modifica su un altro thread sono gli unici mezzi di esistenza per quella classe.


10
Quello che vorrei aggiungere è che qui sono utili informazioni su come e quando utilizzarlo ConcurrentDictionary.
alex.b

1

Dipende, nel mio caso preferisco usare questo metodo.

ConcurrentDictionary<TKey, TValue>.AddOrUpdate Method (TKey, Func<TKey, TValue>, Func<TKey, TValue, TValue>);

Vedere MSDN Library per i dettagli sull'utilizzo del metodo.

Utilizzo del campione:

results.AddOrUpdate(
  Id,
  id => new DbResult() {
     Id = id,
     Value = row.Value,
     Rank = 1
  },
  (id, v) =>
  {
     v.Rank++;
     return v;
  });

2
FYI: "Quando fornisci un metodo value factory (ai metodi GetOrAdd e AddOrUpdate), può effettivamente essere eseguito e il suo risultato viene scartato in seguito (perché qualche altro thread ha vinto la gara)." Maggiori info qui: arbel.net/2013/02/03/…
keremispirli

Sì, hai ragione, come indicato nella sezione dei commenti "Se chiami AddOrUpdate simultaneamente su thread diversi, addValueFactory può essere chiamato più volte, ma la sua coppia chiave / valore potrebbe non essere aggiunta al dizionario per ogni chiamata." Quindi devi essere sicuro di non generare più oggetti persistenti.
Onur

E se è necessario aggiornare i contenuti, senza modificare completamente l'oggetto memorizzato, ad esempio per modificare una proprietà di un oggetto aggiunto in precedenza, questo metodo è utile, altrimenti è necessario utilizzare blocchi o altri metodi di sincronizzazione.
Onur

1

Solo una nota: non giustifica l'utilizzo di un oggetto ConcurrentDicitonary con un ciclo lineare, rendendolo sottoutilizzato. La migliore alternativa è seguire le raccomandazioni della documentazione Microsoft, come menzionato da Oded utilizzando Parallelism, secondo l'esempio seguente:

Parallel.For(0, 4, i => 
{
   myDic.TryAdd(i, 0);
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.