Mam następującą klasę.
class Test{
public HashSet<string> Data = new HashSet<string>();
}
Muszę zmienić pole „Dane” z różnych wątków, więc chciałbym poznać opinie na temat mojej obecnej implementacji bezpiecznej wątkowo.
class Test{
public HashSet<string> Data = new HashSet<string>();
public void Add(string Val){
lock(Data) Data.Add(Val);
}
public void Remove(string Val){
lock(Data) Data.Remove(Val);
}
}
Czy jest lepsze rozwiązanie, aby przejść bezpośrednio do pola i zabezpieczyć je przed równoczesnym dostępem wielu wątków?
System.Collections.Concurrent
ReaderWriterLock
będzie pomocny (wydajny), gdy wielu czytelników i jeden pisarz. Musimy wiedzieć, czy tak jest w przypadku OPOdpowiedzi:
Twoja implementacja jest prawidłowa. .NET Framework nie zapewnia niestety wbudowanego współbieżnego typu hashset. Istnieją jednak pewne obejścia.
ConcurrentDictionary (zalecane)
Pierwszym jest użycie klasy
ConcurrentDictionary<TKey, TValue>
w przestrzeni nazwSystem.Collections.Concurrent
. W takim przypadku wartość jest bezcelowa, więc możemy użyć prostegobyte
(1 bajt w pamięci).Jest to zalecana opcja, ponieważ typ jest bezpieczny dla wątków i zapewnia te same korzyści, co
HashSet<T>
klucz z wyjątkiem i wartość to różne obiekty.Źródło: Social MSDN
ConcurrentBag
Jeśli nie masz nic przeciwko zduplikowanym wpisom, możesz użyć klasy
ConcurrentBag<T>
w tej samej przestrzeni nazw, co poprzednia klasa.Samodzielna realizacja
Wreszcie, tak jak to zrobiłeś, możesz zaimplementować własny typ danych, używając blokady lub innych sposobów, które zapewnia .NET, aby zapewnić bezpieczeństwo wątków. Oto wspaniały przykład: Jak zaimplementować ConcurrentHashSet w .Net
Jedyną wadą tego rozwiązania jest to, że typ
HashSet<T>
nie jest oficjalnie równoczesny, nawet w przypadku operacji odczytu.Cytuję kod posta, do którego prowadzi link (pierwotnie napisany przez Bena Moshera ).
EDYCJA: Przenieś metody blokady wejścia poza
try
bloki, ponieważ mogą one zgłosić wyjątek i wykonać instrukcje zawarte wfinally
blokach.źródło
null
referencją (odwołanie wymaga 4 bajtów w 32-bitowym czasie wykonywania i 8 bajtów w 64-bitowym czasie wykonywania). W związku z tym użyciebyte
pustej struktury lub podobnej może zmniejszyć zużycie pamięci (lub może nie, jeśli środowisko wykonawcze wyrównuje dane w granicach pamięci natywnej w celu uzyskania szybszego dostępu).Zamiast zawijać
ConcurrentDictionary
lub blokować plikHashSet
, utworzyłem rzeczywistyConcurrentHashSet
oparty naConcurrentDictionary
.Ta implementacja obsługuje podstawowe operacje na element bez
HashSet
ustawionych operacji, ponieważ mają one mniej sensu w współbieżnych scenariuszach IMO:Wyjście: 2
Możesz go pobrać z NuGet tutaj i zobaczyć źródło na GitHub tutaj .
źródło
ISet<T>
interfejsu bo faktycznie pasującego doHashSet<T>
semantyki?Overlaps
na przykład musiałby albo zablokować instancję na czas jej działania, albo udzielić odpowiedzi, która może już być błędna. Obie opcje są złe IMO (i mogą być dodawane zewnętrznie przez konsumentów).Ponieważ nikt inny o tym nie wspomniał, zaproponuję alternatywne podejście, które może, ale nie musi być odpowiednie dla twojego konkretnego celu:
Niezmienne kolekcje firmy Microsoft
Z posta na blogu zespołu MS odpowiedzialnego za:
Te kolekcje obejmują ImmutableHashSet <T> i ImmutableList <T> .
Występ
Ponieważ niezmienne kolekcje używają drzewiastych struktur danych pod spodem, aby umożliwić współużytkowanie strukturalne, ich charakterystyki wydajnościowe różnią się od kolekcji zmiennych. W porównaniu z blokującą kolekcją mutowalną, wyniki będą zależeć od rywalizacji o blokady i wzorców dostępu. Jednak zaczerpnięte z innego posta na blogu o niezmiennych kolekcjach:
Innymi słowy, w wielu przypadkach różnica nie będzie zauważalna i powinieneś wybrać prostszy wybór - który w przypadku zestawów współbieżnych byłby odpowiedni
ImmutableHashSet<T>
, ponieważ nie masz istniejącej blokującej mutowalnej implementacji! :-)źródło
ImmutableHashSet<T>
niewiele pomaga, jeśli zamierzasz zaktualizować stan udostępniony z wielu wątków lub czy czegoś tu brakuje?ImmutableInterlocked.Update
wydaje się być brakującym ogniwem. Dziękuję Ci!Najtrudniejsze w tworzeniu
ISet<T>
współbieżności jest to, że metody zestawu (suma, przecięcie, różnica) mają charakter iteracyjny. Musisz przynajmniej wykonać iterację po wszystkich n elementach jednego ze zbiorów zaangażowanych w operację, jednocześnie blokując oba zestawy.Tracisz zalety a,
ConcurrentDictionary<T,byte>
gdy musisz zablokować cały zestaw podczas iteracji. Bez blokowania operacje te nie są bezpieczne dla wątków.Biorąc pod uwagę dodatkowe koszty
ConcurrentDictionary<T,byte>
, prawdopodobnie rozsądniej jest po prostu użyć mniejszej wagiHashSet<T>
i po prostu otoczyć wszystko zamkami.Jeśli nie potrzebujesz operacji na zestawach, użyj
ConcurrentDictionary<T,byte>
i po prostu użyjdefault(byte)
jako wartości podczas dodawania kluczy.źródło
Wolę kompletne rozwiązania, więc zrobiłem to: Pamiętaj, że mój Count jest zaimplementowany w inny sposób, ponieważ nie widzę, dlaczego należy zabronić odczytywania hashsetu podczas próby policzenia jego wartości.
@Zen, dziękujemy za rozpoczęcie.
źródło
EnterWriteLock
, dlaczego wEnterReadLock
ogóle istnieje? Czy nie można użyć blokady odczytu do metod takich jakContains
?