2011-01-14 10 views
10

W kontekście niniejszego sprawozdania,Wyjaśnienie zapisu i odczytu na C# Dictionary

A Dictionary może obsługiwać jednocześnie wielu czytelników, jak długo jako zbiór nie jest modyfikowany. Mimo to wyliczenie poprzez kolekcję nie jest z natury procedurą bezpieczną dla wątków. W rzadkich przypadku gdy wyliczenie twierdzi z zapisem dostęp kolekcja musi być zamknięty podczas całego wyliczenie. Aby umożliwić dostęp do kolekcji przez wiele wątków do czytania i pisania , musisz wykonać własną synchronizację.

Co oznacza odczyt i zapis? Rozumiem, że odczyt to operacja, która wyszukuje klucz i zawiera odniesienie do jego wartości, a zapis to operacja, która dodaje lub usuwa parę wartości klucza ze słownika. Jednak nie mogę znaleźć niczego rozstrzygającego w tej kwestii.

Więc pytanie brzmi, realizując bezpieczną słownika wątek, to operacja, która aktualizuje wartość do istniejącego klucza w słowniku należy rozważyć czytelnika lub pisarza? Planuję mieć wiele wątków uzyskujących dostęp do unikalnych kluczy w słowniku i modyfikowanie ich wartości, ale wątki nie będą dodawać/usuwać nowych kluczy.

Oczywistą konsekwencją, zakładając modyfikację istniejącej wartości, nie jest operacja zapisu w słowniku, jest to, że moja implementacja dictionay safe dictionay może być dużo bardziej efektywna, ponieważ nie potrzebowałbym uzyskać wyjątkowego blokady za każdym razem Próbuję zaktualizować wartość do istniejącego klucza.

Używanie ConcurrentDictionary z .Net 4.0 nie jest opcją.

Odpowiedz

2

zastąpieniem istniejącego wartość powinna być traktowana jako operacja zapisu

+2

To naprawdę komentarz, a nie odpowiedź na pytanie. Użyj "dodaj komentarz", aby wystawić opinię dla autora. – SliverNinja

+0

@SliverNinja, wydaje mi się, że ta odpowiedź odnosi się do podstawowego pytania "czy operacja, która aktualizuje wartość istniejącego klucza w słowniku, powinna być uważana za czytelnika lub pisarza?". – binki

-1

Modyfikowanie wartości to write i wprowadza warunek wyścigu.

Powiedzmy oryginalna wartość mydict [5] = 42. Jeden aktualizacje gwint mydict [5], aby być 112. innym wątku aktualizacje mydict [5], aby być 837.

Co jeśli wartość mydict [5] być na końcu? Kolejność wątków jest w tym przypadku ważna, co oznacza, że ​​musisz upewnić się, że zamówienie jest jednoznaczne lub że nie pisze.

+1

To, co opisujesz, nie jest tak naprawdę stanem wyścigowym - ponieważ tak czy owak, ktokolwiek to zrobi, wygrywa - i to samo byłoby, gdybyś zablokował dostęp. Warunek wyścigowy może wyglądać następująco: jeśli (! Dict.ContainsKey (5)) dict (5, nowa wartość()) istnieje teraz potencjalna niespójność. – Neil

+0

@Neil Myślę, że to warunek wyścigowy, po prostu nie jest to stan wyścigowy, z którym słownik może sobie poradzić. Założyłem już kod, aby upewnić się, że rodzaj warunku wyścigu wymieniony w odpowiedzi nie ma miejsca. – Joshua

+3

@Joshua - może wystąpić warunek wyścigowy w kodzie słownika, który implementuje 'mydict [5] = X', ale nie ma żadnych warunków wyścigowych pomiędzy instrukcjami Set 3 Serinusa. Kto kiedykolwiek ustawi wartość jako ostatnią wygrywa - to tylko opis normalnego programowania. Stan wyścigu występuje w przypadku, gdy ustawisz stan na podstawie wcześniejszego sprawdzenia stanu. Samodzielnie x = 1 nie może wygenerować warunku wyścigowego x ++ może. – Neil

0

Wszystko, co może wpływać na wyniki innego odczytu, należy traktować jako zapis.

Zmiana klucza jest najbardziej zdecydowanie się pisać, ponieważ spowoduje to element, który ma ruszyć w wewnętrznej hash indeksu lub słowniki jednak wykonywać swoją O (log (n)) rzeczy ...

Co warto zrobić, to patrzeć na ReaderWriterLock

http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlock.aspx

+2

Wiele dokumentacji, którą przeczytałem, pokazuje, że blokada czytnika/pisaka ma szokująco słabą wydajność, co jest gorsze niż po prostu przy użyciu pojedynczego zamka. Wygląda na to, że ReaderWriterLockSlim powinien być tym, czego używasz, jeśli jest dostępny lub używasz czegoś innego. http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx –

+0

@Erik - zgadzam się, ale dodam: zawsze jest równowaga. Musiałoby to zależeć od innych kosztów w miksie. Jeśli ilość czytania i pisania jest w przybliżeniu równa, to radziłbym po prostu używać lock() - ale jeśli pisanie jest rzadkie - i zwykle jest to wtedy blokada czytnika Reader prawdopodobnie jest w porządku. Po uaktualnieniu łatwo będzie przejść do blokady Slim. Profilowanie, gdy masz problem, jest najlepszym sposobem na osiągnięcie wydajności. Bardzo często ludzie spędzają dni na kodowaniu, aby zaoszczędzić milisekundy na operację. – Neil

+0

Uzgodniono, że czasami deweloperzy wydają zbyt wiele prób wstępnej optymalizacji. Użyłem zwrotu "szokująco słaby występ", aby zaznaczyć, że nie jest to subtelny problem z wydajnością, ale raczej drastyczny. Zazwyczaj uważa się, że lepiej go unikać niż go używać. Przynajmniej wydaje mi się, że wiele wniosków zostało wyciągniętych podczas różnych dyskusji na temat wyników. –

0

Aktualizacja wartości jest koncepcyjnie operacja zapisu. Podczas aktualizowania wartości z równoczesnym dostępem, gdy odczyt jest wykonywany przed zakończeniem zapisu, odczytujesz starą wartość. Kiedy dwa zapisy powodują konflikt, można zapisać niewłaściwą wartość.

Dodanie nowej wartości może spowodować wzrost pamięci bazowej. W tym przypadku przydzielana jest nowa pamięć, wszystkie elementy są kopiowane do nowej pamięci, dodawany jest nowy element, obiekt słownika jest aktualizowany, aby odnosił się do nowej lokalizacji pamięci do przechowywania, a stara pamięć jest zwalniana i dostępna do usuwania śmieci. W tym czasie więcej zapisów może spowodować duży problem. Dwa zapisy w tym samym czasie mogą wywołać dwa wystąpienia tego kopiowania pamięci. Jeśli będziesz postępować zgodnie z logiką, zauważysz, że element zostanie zgubiony, ponieważ tylko ostatni wątek aktualizujący odniesienie będzie wiedział o istniejących elementach, a nie o innych elementach, które próbowano dodać.

ICollection provides a member to synchronize access i referencja zachowuje ważność w operacjach rozwijania/zmniejszania.

+0

To jest interesujący punkt, jeśli zadeklaruję słownik i insert ("Key1", "Hello"), a następnie do katalogu ["Key1"] = "To jest naprawdę naprawdę długi ciąg", czy to trzeba ponownie przydzielić pamięć dla wartości, lub ponieważ wartość jest oject, a pamięć została już przydzielona dla obiektu, byłaby w stanie ponownie wykorzystać tę pamięć? Sądzę, że wiele zależy od wnętrzności obiektu Dictionary. – Joshua

+0

Dla ciągu bazowa pamięć to ICollection >. Fakt, że już wprowadziłeś wartość oznacza, że ​​rekord został dodany do ICollection (rozszerzenie jego zawartości, zwiększenie pamięci w razie potrzeby). Zawartość jest po prostu strukturą zawierającą odniesienie do dwóch obiektów typu string. Zmiana łańcucha wartości słownika na coś innego tylko tworzy nowy ciąg w pamięci i aktualizuje odniesienie KeyValuePair. Bez zmiany rozmiaru pamięci masowej IDictionary. –

+0

Ponadto Insert spowoduje zgłoszenie wyjątku, jeśli spróbujesz dwukrotnie wstawić ten sam klucz. Może się to zdarzyć w środowisku wielowątkowym, w którym blokowanie nie zostało prawidłowo zaimplementowane. Jeśli chcesz być bezpieczniejszy, po prostu dostęp do nieistniejącego klucza spowoduje jego utworzenie. Jeśli istnieje, zostanie zaktualizowany. Bez wyjątku. Użyj Insert, jeśli dodanie tego samego klucza jest faktycznym warunkiem błędu, który chcesz przechwycić. directory.Insert ("Key1", "Hello"); versus directory ["Key1"] = "Hello"; –

0

Operacją odczytu jest wszystko, co dostaje klucz lub wartość z Dictionary, operacja zapisu to coś, co aktualizuje lub dodaje klucz lub wartość. Zatem proces aktualizujący klucz byłby uważany za pisarza.

Prosty sposób na bezpieczną słownika wątek jest stworzyć własną implementację IDictionary że po prostu blokuje mutex a następnie przekierowuje połączenie do realizacji:

public class MyThreadSafeDictionary<T, J> : IDictionary<T, J> 
{ 
     private object mutex = new object(); 
     private IDictionary<T, J> impl; 

     public MyThreadSafeDictionary(IDictionary<T, J> impl) 
     { 
      this.impl = impl; 
     } 

     public void Add(T key, J value) 
     { 
     lock(mutex) { 
      impl.Add(key, value); 
     } 
     } 

     // implement the other methods as for Add 
} 

Można zastąpić mutex z czytnikiem -writer lock, jeśli masz jakieś wątki, tylko czytasz słownik.

Należy również pamiętać, że obiekty Dictionary nie obsługują zmiany kluczy; jedynym bezpiecznym sposobem osiągnięcia pożądanego efektu jest usunięcie istniejącej pary klucz/wartość i dodanie nowej z zaktualizowanym kluczem.

+0

jest jakiś powód, aby to zrobić, zamiast używać ConcurrentDictionary? – mcmillab

3

Głównym punktem jeszcze nie wspomniano, że jeśli TValue jest typem klasy, rzeczy posiadane przez Dictionary<TKey,TValue> będą tożsamości TValue obiektów. Jeśli otrzymamy referencję ze słownika, słownik nie będzie wiedział ani nie będzie dbał o nic, co można zrobić z obiektem, do którego się odnosi.

Przydatnym trochę klasy narzędzie w przypadkach, gdy wszystkie klucze związane ze słownikiem będą znane wcześniej kodu, który potrzebuje go używać to:

class MutableValueHolder<T> 
{ 
    public T Value; 
} 

Jeśli ktoś chce mieć wielowątkowy kod Policz ile razy różne ciągi pojawiają się w paczce plików, a każdy z nich zna z góry wszystkie interesujące sygnały, można użyć do tego celu czegoś podobnego do Dictionary<string, MutableValueHolder<int>>. Po załadowaniu słownika wszystkimi odpowiednimi ciągami i instancją MutableValueHolder<int> dla każdej z nich, dowolna liczba wątków może pobrać odwołania do obiektów MutableValueHolder<int> i użyć Threading.Interlocked.Increment lub innych takich metod do zmodyfikowania Value powiązanych z każdym z nich, bez konieczności pisania do słownika w ogóle.

Powiązane problemy