9

Il seguente codice spiega la mia domanda. So che l'elenco non è thread-safe. Ma qual è la sottostante "vera" ragione di ciò?più thread che aggiungono elementi a una lista. perché ci sono sempre meno articoli nella lista del previsto?

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<string> strCol = new List<string>(); 

     for (int i = 0; i < 10; i++) 
     { 
      int id = i; 
      Task.Factory.StartNew(() => 
      { 
       AddElements(strCol); 
      }).ContinueWith((t) => { WriteCount(strCol, id.ToString()); }); 
     } 

     Console.ReadLine(); 
    } 

    private static void WriteCount(List<string> strCol, string id) 
    { 
     Console.WriteLine(string.Format("Task {0} is done. Count: {1}. Thread ID: {2}", id, strCol.Count, Thread.CurrentThread.ManagedThreadId)); 
    } 

    private static void AddElements(List<string> strCol) 
    { 
     for (int i = 0; i < 20000; i++) 
     { 
      strCol.Add(i.ToString()); 
     } 
    } 
} 
+1

sembra che alcuni dei fili si scrivano uno sull'altro. L'elenco interno deve utilizzare un contatore per passare alla posizione successiva e, poiché non è protetto da thread, i suoi valori di sovrascrittura –

risposta

15

Salterò l'ovvia risposta "Elenco non è thread-safe" - questo lo sai già.

Gli elementi di elenco vengono mantenuti in un array interno. Ci sono almeno due fasi (dal punto di vista logico) quando si aggiunge un elemento a un elenco. Innanzitutto, Elenco ottiene un indice che indica dove inserire un nuovo elemento. Mette il nuovo oggetto in array usando questo indice. Quindi aumenta l'indice e questa è la fase due. Se il secondo (o terzo, quarto, ...) thread si aggiunge per aggiungere un nuovo elemento nello stesso momento potrebbe essere che ci saranno due (3, 4, ...) nuovi oggetti messi nella stessa posizione dell'array prima dell'indice viene incrementato dal primo thread. Gli articoli vengono sovrascritti e persi.

Le operazioni interne di aggiunta di un nuovo elemento e di un indice di incremento devono sempre essere eseguite in una volta sola affinché l'elenco sia sicuro. Questa è la sezione critica. Questo può essere ottenuto con le serrature.

Spero che questo spieghi un po '.

13

Questo perché List<T> non è thread-safe.

È necessario utilizzare una raccolta thread-safe per questo, ad esempio una delle raccolte in System.Collections.Concurrent. In caso contrario, sarà necessario sincronizzare tutti gli accessi allo List<T> (ad es .: mettere ogni chiamata Add all'interno di un blocco), che vanificherà lo scopo di chiamare questo utilizzando più thread interamente, poiché non si sta facendo altro in questa situazione .

+0

come indicato nella domanda. So che la lista non è sicura. Ma qual è la sottostante "vera" ragione di ciò? – CuiPengFei

+2

@CuiPengFei la ragione "reale" è probabile che ci siano operazioni (copie di dati, ridimensionamenti di array, ecc.) Che vengono eseguite senza blocco, nel presupposto che lo sviluppatore non interagisca con l'elenco da più thread contemporaneamente. La sincronizzazione aggiuntiva richiesta per rendere sicuro il thread 'List' avrebbe un impatto significativo sulle prestazioni, e quindi i progettisti BCL hanno scelto di ometterlo e documentare la mancanza di sicurezza del thread in modo che se hai bisogno dell'accesso sicuro ai thread puoi crearlo da solo sull'elenco. Come in 'ArrayList' di Java e in molte altre classi di' List'. –

+0

@CuiPengFei: Perché, non essendo thread-safe, l'atto di aggiungere a 'List ' non è completamente atomico. Un thread inizia ad aggiungere un elemento, un altro thread cortocircuitalo aggiungendo il proprio elemento. – David

Problemi correlati