Rozumiem, że generalnie lista nie jest bezpieczna dla wątków, ale czy jest coś złego w zwykłym dodawaniu elementów do listy, jeśli wątki nigdy nie wykonują żadnych innych operacji na liście (takich jak przechodzenie przez nią)?
Przykład:
List<object> list = new List<object>();
Parallel.ForEach(transactions, tran =>
{
list.Add(new object());
});
Odpowiedzi:
Za kulisami dzieje się wiele rzeczy, w tym ponowne przydzielanie buforów i kopiowanie elementów. Ten kod spowoduje niebezpieczeństwo. Po prostu nie ma żadnych niepodzielnych operacji podczas dodawania do listy, przynajmniej właściwość „Długość” musi zostać zaktualizowana, a element należy umieścić we właściwym miejscu, a (jeśli istnieje oddzielna zmienna) indeks potrzebuje do zaktualizowania. Wiele wątków może się nadepnąć. A jeśli wymagana jest uprawa, dzieje się o wiele więcej. Jeśli coś pisze na liście, nic innego nie powinno czytać ani pisać do niej.
W .NET 4.0 mamy współbieżne kolekcje, które są bezpieczne dla wątków i nie wymagają blokad.
źródło
ConcurrentList
typu. Istnieją równoczesne torby, słowniki, stosy, kolejki itp., Ale nie ma list.Twoje obecne podejście nie jest bezpieczne dla wątków - sugerowałbym całkowite unikanie tego - ponieważ zasadniczo wykonujesz transformację danych PLINQ może być lepszym podejściem (wiem, że to jest uproszczony przykład, ale na końcu projektujesz każdą transakcję w inny "stan ”obiekt).
List<object> list = transactions.AsParallel() .Select( tran => new object()) .ToList();
źródło
Parallel.Foreach
przeciążenie, które pobiera indeks - w takim przypadku każdy wątek manipuluje innym wpisem tablicy i powinieneś być bezpieczny.Nie jest to nierozsądne pytanie. Tam są przypadki, w których metody, które mogą powodować problemy z wątku bezpieczeństwa w połączeniu z innymi metodami są bezpieczne, jeżeli są one tylko metoda nazywa.
Jednak ewidentnie tak nie jest, jeśli weźmiemy pod uwagę kod pokazany w reflektorze:
public void Add(T item) { if (this._size == this._items.Length) { this.EnsureCapacity(this._size + 1); } this._items[this._size++] = item; this._version++; }
Nawet jeśli
EnsureCapacity
sam w sobie był wątkowo bezpieczny (a na pewno nie jest), powyższy kod z pewnością nie będzie bezpieczny wątkowo, biorąc pod uwagę możliwość jednoczesnego wywołania operatora przyrostu powodującego błędy w zapisie.Albo zablokuj, użyj ConcurrentList, albo może użyj kolejki bez blokad jako miejsca, w którym zapisuje wiele wątków i odczytu z niej - bezpośrednio lub przez wypełnienie listy - po wykonaniu swojej pracy (zakładam, że wielokrotne jednoczesne zapisy, po których następuje odczyt jednowątkowy, jest tutaj twoim wzorcem, sądząc po twoim pytaniu, ponieważ w przeciwnym razie nie widzę, jak warunek, w którym
Add
jest jedyną wywołaną metodą, mógłby się przydać).źródło
Jeśli chcesz używać
List.add
z wielu wątków i nie dbasz o kolejność, prawdopodobnie i tak nie potrzebujesz możliwości indeksowaniaList
i zamiast tego powinieneś użyć niektórych dostępnych kolekcji współbieżnych.Jeśli zignorujesz tę radę i tylko to zrobisz
add
, możesz sprawić, żeadd
wątek będzie bezpieczny, ale w nieprzewidywalnej kolejności:private Object someListLock = new Object(); // only once ... lock (someListLock) { someList.Add(item); }
Jeśli zaakceptujesz tę nieprzewidywalną kolejność, prawdopodobnie, jak wspomniano wcześniej, nie potrzebujesz kolekcji, która może wykonywać indeksowanie jak w
someList[i]
.źródło
Spowodowałoby to problemy, ponieważ lista jest zbudowana na tablicy i nie jest bezpieczna dla wątków, możesz uzyskać indeks poza granicami wyjątku lub niektóre wartości przesłaniają inne wartości, w zależności od tego, gdzie znajdują się wątki. Zasadniczo nie rób tego.
Istnieje wiele potencjalnych problemów ... Po prostu nie rób tego. Jeśli potrzebujesz kolekcji bezpiecznej wątkowo, użyj blokady lub jednej z kolekcji System.Collections.Concurrent.
źródło
Rozwiązałem swój problem, używając
ConcurrentBag<T>
zamiastList<T>
tego:ConcurrentBag<object> list = new ConcurrentBag<object>(); Parallel.ForEach(transactions, tran => { list.Add(new object()); });
źródło
Krótka odpowiedź: tak.
Długa odpowiedź: uruchom poniższy program.
using System; using System.Collections.Generic; using System.Linq; using System.Threading; class Program { readonly List<int> l = new List<int>(); const int amount = 1000; int toFinish = amount; readonly AutoResetEvent are = new AutoResetEvent(false); static void Main() { new Program().Run(); } void Run() { for (int i = 0; i < amount; i++) new Thread(AddTol).Start(i); are.WaitOne(); if (l.Count != amount || l.Distinct().Count() != amount || l.Min() < 0 || l.Max() >= amount) throw new Exception("omg corrupted data"); Console.WriteLine("All good"); Console.ReadKey(); } void AddTol(object o) { // uncomment to fix // lock (l) l.Add((int)o); int i = Interlocked.Decrement(ref toFinish); if (i == 0) are.Set(); } }
źródło
Jak już powiedzieli inni, możesz używać kolekcji współbieżnych z
System.Collections.Concurrent
przestrzeni nazw. Jeśli możesz użyć jednego z nich, jest to preferowane.Ale jeśli naprawdę potrzebujesz listy, która jest właśnie zsynchronizowana, możesz spojrzeć na
SynchronizedCollection<T>
-Class wSystem.Collections.Generic
.Zauważ, że musiałeś dołączyć zestaw System.ServiceModel, co jest również powodem, dla którego nie lubię go tak bardzo. Ale czasami go używam.
źródło
Nawet dodawanie elementów do różnych wątków nie jest bezpieczne dla wątków.
W C # 4.0 istnieją współbieżne kolekcje (patrz http://jiezhu0815.blogspot.com/2010/08/c-40-feature-1-concurrent-collections.html ).
źródło