9

Poniższy kod wyjaśnia moje pytanie. Wiem, że lista nie jest bezpieczna dla wątków. Ale jaki jest podstawowy "prawdziwy" powód tego?wiele wątków dodających elementy do jednej listy. dlaczego zawsze na liście jest mniej przedmiotów niż się spodziewano?

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

Wygląda na to, że niektóre wątki piszą o sobie nawzajem. Wewnętrzna lista musi wykorzystywać licznik do zwiększenia do następnej pozycji, a ponieważ nie jest bezpieczna dla wątków, jej wartości nadpisujące są podane jako –

Odpowiedz

15

Pomijam oczywistą odpowiedź "Lista nie jest bezpieczna dla wątków" - to już wiesz.

Elementy listy przechowywane są w wewnętrznej tablicy. Przy dodawaniu elementu do listy istnieją co najmniej dwa etapy (z logicznego punktu widzenia). Najpierw lista otrzymuje indeks wskazujący, gdzie umieścić nowy przedmiot. Wprowadza nowy element do tablicy za pomocą tego indeksu. Następnie zwiększa indeks, a jest to etap drugi. Jeśli drugi (lub trzeci, ...) wątek będzie dodawał nowy element w tym samym czasie, może się zdarzyć, że dwa nowe (3, 4, ...) nowe elementy zostaną umieszczone w tej samej lokalizacji tablicy przed indeksem jest zwiększany o pierwszy wątek. Elementy są nadpisywane i tracone.

Wewnętrzne operacje dodawania nowego elementu i zwiększania indeksu muszą być zawsze wykonywane za jednym razem, aby lista była bezpieczna dla wątków. To się nazywa sekcja krytyczna. Można to osiągnąć za pomocą zamków.

Mam nadzieję, że to trochę wyjaśnia.

13

Dzieje się tak, ponieważ List<T> nie jest bezpieczny dla wątków.

Do tego celu należy użyć kolekcji bezpiecznej dla wątków, takiej jak jedna z kolekcji w System.Collections.Concurrent. W przeciwnym razie będziesz musiał zsynchronizować cały dostęp do List<T> (tj. Umieścić każde wywołanie Add w zamku), co spowoduje całkowite unieważnienie wywołania tego przy użyciu wielu wątków, ponieważ nie wykonujesz żadnej innej pracy w tej sytuacji .

+0

, jak podano w pytaniu. Wiem, że lista nie jest bezpieczna dla wątków. Ale jaki jest podstawowy "prawdziwy" powód tego? – CuiPengFei

+2

@CuiPengFei "prawdziwy" powód jest prawdopodobnie taki, że istnieją operacje (kopie danych, zmiana rozmiaru tablicy itp.), Które są wykonywane bez blokowania, przy założeniu, że programista nie wchodzi w interakcję z 'List' z wielu wątków naraz. Dodatkowa synchronizacja wymagana do zabezpieczenia wątku 'List' będzie miała znaczący wpływ na wydajność, dlatego też projektanci BCL zdecydowali się go pominąć i udokumentować brak bezpieczeństwa wątków, więc jeśli potrzebujesz bezpiecznego dostępu do wątków, możesz go sam zbudować wokół listy. To samo, co w Java 'ArrayList' i wielu innych klasach' List'. –

+0

@CuiPengFei: Ponieważ nie jest bezpieczny dla wątków, dodanie do 'List ' nie jest całkowicie atomowe. Jeden wątek zaczyna dodawać element, inny wątek zwiera go, dodając własny element. – David