Nie mogłem znaleźć wystarczających informacji o ConcurrentDictionary
typach, więc pomyślałem, że zapytam o to tutaj.
Obecnie używam a, Dictionary
aby zatrzymać wszystkich użytkowników, do których stale uzyskuje dostęp wiele wątków (z puli wątków, więc nie ma dokładnej liczby wątków) i ma zsynchronizowany dostęp.
Niedawno dowiedziałem się, że w .NET 4.0 był zestaw bezpiecznych wątków i wydaje się to być bardzo przyjemne. Zastanawiałem się, jaka byłaby opcja `` bardziej wydajna i łatwiejsza w zarządzaniu '', ponieważ mam opcję między posiadaniem normalnego Dictionary
z zsynchronizowanym dostępem lub posiadaniem tego, ConcurrentDictionary
który jest już bezpieczny dla wątków.
Odpowiedzi:
Kolekcja bezpieczna wątkowo w porównaniu z kolekcją bez ochrony wątków może być postrzegana w inny sposób.
Rozważmy sklep bez sprzedawcy, z wyjątkiem kasy. Masz mnóstwo problemów, jeśli ludzie nie działają odpowiedzialnie. Na przykład, powiedzmy, że klient bierze puszkę z piramidy - może, podczas gdy urzędnik obecnie buduje piramidę, rozpętałoby się piekło. A co, jeśli dwóch klientów sięgnie po ten sam przedmiot w tym samym czasie, kto wygrywa? Czy będzie walka? To jest kolekcja bez ochrony wątków. Istnieje wiele sposobów na uniknięcie problemów, ale wszystkie wymagają pewnego rodzaju blokowania lub raczej jawnego dostępu w taki czy inny sposób.
Z drugiej strony, rozważ sklep z urzędnikiem przy biurku, a zakupy możesz robić tylko za jego pośrednictwem. Stajesz w kolejce i prosisz go o przedmiot, on przynosi ci go z powrotem, a ty wychodzisz z kolejki. Jeśli potrzebujesz wielu przedmiotów, możesz odebrać tylko tyle przedmiotów w każdej podróży w obie strony, ile pamiętasz, ale musisz uważać, aby nie zapychać urzędnika, to złości innych klientów w kolejce za tobą.
Teraz rozważ to. W sklepie z jednym urzędnikiem, co, jeśli dojdziesz do pierwszej linii i zapytasz sprzedawcę „Czy masz papier toaletowy”, a on powie „Tak”, a potem powiesz „Ok, ja” Skontaktuję się z Tobą, kiedy będę wiedział, ile potrzebuję ”, a gdy wrócisz na początek kolejki, sklep może oczywiście zostać wyprzedany. Ten scenariusz nie jest chroniony przez kolekcję z ochroną wątków.
Kolekcja z ochroną wątków gwarantuje, że jej wewnętrzne struktury danych są zawsze prawidłowe, nawet jeśli uzyskiwany jest do nich dostęp z wielu wątków.
Kolekcja bez ochrony wątków nie jest objęta żadnymi takimi gwarancjami. Na przykład, jeśli dodasz coś do drzewa binarnego w jednym wątku, podczas gdy inny wątek jest zajęty równoważeniem drzewa, nie ma gwarancji, że element zostanie dodany, a nawet, że drzewo będzie nadal ważne później, może być uszkodzone bez nadziei.
Kolekcja z ochroną wątków nie gwarantuje jednak, że wszystkie sekwencyjne operacje w wątku działają na tej samej „migawce” wewnętrznej struktury danych, co oznacza, że jeśli masz taki kod:
if (tree.Count > 0) Debug.WriteLine(tree.First().ToString());
możesz otrzymać NullReferenceException, ponieważ pomiędzy
tree.Count
atree.First()
inny wątek wyczyścił pozostałe węzły w drzewie, co oznacza,First()
że powrócinull
.W tym scenariuszu musisz albo sprawdzić, czy dana kolekcja ma bezpieczny sposób na uzyskanie tego, czego chcesz, być może musisz przepisać powyższy kod lub może być konieczne zablokowanie.
źródło
Dictionary
i samodzielną obsługą blokowania a używaniemConcurrentDictionary
typu wbudowanego w .NET 4+. Właściwie jestem trochę zaskoczony, że zostało to zaakceptowane.Nadal musisz zachować ostrożność podczas korzystania z kolekcji bezpiecznych wątkowo, ponieważ bezpieczeństwo wątków nie oznacza, że możesz zignorować wszystkie problemy z wątkami. Gdy kolekcja reklamuje się jako bezpieczna dla wątków, zwykle oznacza to, że pozostaje w spójnym stanie, nawet gdy wiele wątków odczytuje i zapisuje jednocześnie. Nie oznacza to jednak, że pojedynczy wątek zobaczy „logiczną” sekwencję wyników, jeśli wywoła wiele metod.
Na przykład, jeśli najpierw sprawdzisz, czy klucz istnieje, a następnie uzyskasz wartość odpowiadającą kluczowi, ten klucz może już nie istnieć nawet w wersji ConcurrentDictionary (ponieważ inny wątek mógł usunąć klucz). Nadal musisz użyć blokowania w tym przypadku (lub lepiej: połączyć dwa wywołania przy użyciu TryGetValue ).
Więc używaj ich, ale nie myśl, że daje to darmową przepustkę do zignorowania wszystkich problemów z współbieżnością. Nadal musisz być ostrożny.
źródło
TryGetValue
zawsze było częściąDictionary
i zawsze było zalecanym podejściem. Ważnymi „współbieżnymi” metodami, któreConcurrentDictionary
wprowadza, sąAddOrUpdate
iGetOrAdd
. Dobra odpowiedź, ale mogłem wybrać lepszy przykład.Wewnętrznie ConcurrentDictionary używa oddzielnej blokady dla każdego zasobnika mieszania. Tak długo, jak używasz tylko Add / TryGetValue i podobnych metod, które działają na pojedynczych wpisach, słownik będzie działał jako prawie pozbawiona blokad struktura danych z odpowiednią zaletą wydajności. OTOH metody wyliczania (w tym właściwość Count) blokują wszystkie zasobniki naraz i dlatego są gorsze niż zsynchronizowany słownik pod względem wydajności.
Powiedziałbym, po prostu użyj ConcurrentDictionary.
źródło
Myślę, że metoda ConcurrentDictionary.GetOrAdd jest dokładnie tym, czego potrzebuje większość scenariuszy wielowątkowych.
źródło
Czy widzieliście reaktywne rozszerzenia dla .Net 3.5sp1. Według Jona Skeeta, oni przenieśli pakiet równoległych rozszerzeń i współbieżnych struktur danych dla .Net3.5 sp1.
Istnieje zestaw próbek dla .Net 4 Beta 2, który dość dobrze opisuje, jak używać ich równoległych rozszerzeń.
Właśnie spędziłem ostatni tydzień na testowaniu ConcurrentDictionary przy użyciu 32 wątków do wykonywania operacji we / wy. Wygląda na to, że działa zgodnie z reklamą, co wskazywałoby, że włożono w to ogromną ilość testów.
Edycja : .NET 4 ConcurrentDictionary i Patterns.
Firma Microsoft wydała plik PDF o nazwie Patterns of Paralell Programming. Naprawdę warto go pobrać, ponieważ opisał w naprawdę fajnych szczegółach właściwe wzorce do użycia dla rozszerzeń .Net 4 Concurrent i wzorce anty, których należy unikać. Tutaj jest.
źródło
Zasadniczo chcesz korzystać z nowego ConcurrentDictionary. Od razu po wyjęciu z pudełka musisz napisać mniej kodu, aby programy były bezpieczne dla wątków.
źródło
ConcurrentDictionary
Jest świetnym rozwiązaniem, jeśli spełnia wszystkie Twoje potrzeby wątku bezpieczeństwa. Jeśli tak nie jest, innymi słowy, jeśli robisz coś nieco złożonego, normalnyDictionary
+lock
może być lepszą opcją. Na przykład załóżmy, że dodajesz jakieś zamówienia do słownika i chcesz na bieżąco aktualizować ich łączną kwotę. Możesz napisać taki kod: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; }
Ten kod nie jest bezpieczny wątkowo. Wiele wątków aktualizujących
_totalAmount
pole może pozostawić je w stanie uszkodzonym. Możesz więc spróbować go chronić za pomocąlock
:_dict.TryAdd(order.Id, order); lock (_locker) _totalAmount += order.Amount;
Ten kod jest „bezpieczniejszy”, ale nadal nie jest bezpieczny dla wątków. Nie ma gwarancji, że
_totalAmount
jest on zgodny z hasłami w słowniku. Inny wątek może próbować odczytać te wartości, aby zaktualizować element interfejsu użytkownika:Decimal totalAmount; lock (_locker) totalAmount = _totalAmount; UpdateUI(_dict.Count, totalAmount);
totalAmount
Nie może odpowiadać liczbie zleceń w słowniku. Wyświetlane statystyki mogą być błędne. W tym momencie zdasz sobie sprawę, że musisz rozszerzyćlock
ochronę o aktualizację słownika:// 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);
Ten kod jest całkowicie bezpieczny, ale wszystkie korzyści z używania go
ConcurrentDictionary
zniknęły.Dictionary
, ponieważ wewnętrzne blokowanieConcurrentDictionary
jest teraz marnotrawne i zbędne.TryAdd
?,AddOrUpdate
?).Tak więc moja rada brzmi: zacznij od
Dictionary
+lock
i zachowaj opcję późniejszej aktualizacji do aConcurrentDictionary
jako optymalizacji wydajności, jeśli ta opcja jest faktycznie wykonalna. W wielu przypadkach tak się nie stanie.źródło
Użyliśmy ConcurrentDictionary do zbuforowanej kolekcji, która jest ponownie wypełniana co 1 godzinę, a następnie odczytywana przez wiele wątków klienta, podobnie jak rozwiązanie dla Czy ten przykładowy wątek jest bezpieczny? pytanie.
Okazało się, że zmiana go na ReadOnlyDictionary poprawiła ogólną wydajność.
źródło