Koncepcja blokad odczytu-zapisu jest prosta – pozwala wielu wątkom jednocześnie uzyskać blokady odczytu, ale tylko jeden wątek może jednocześnie uzyskać blokady zapisu, dlatego nazywa się je również zamkami wyłącznymi współdzielonymi. W C# zaleca się użycie klasy ReaderWriterLockSlim do ukończenia funkcji blokady odczytu/zapisu. W niektórych przypadkach liczba odczytów obiektu jest znacznie większa niż liczba modyfikacji, a jeśli obiekt jest po prostu zablokowany przez blokadę, wpływa to na efektywność odczytu. Jeśli użyty jest blok odczyt-zapis, wiele wątków może jednocześnie odczytywać obiekt i będzie on blokowany tylko wtedy, gdy obiekt jest zajęty przez blokadę zapisu. Mówiąc prosto, gdy wątek wchodzi w tryb czytania, inne wątki nadal mogą wejść w tryb odczytu, zakładając, że wątek chce wejść w tryb zapisu w tym momencie, to musi zostać zablokowany. Aż do wyjścia trybu odczytu. Podobnie, jeśli wątek przejdzie w tryb zapisu, pozostałe wątki zostaną zablokowane, niezależnie od tego, czy chcą pisać, czy czytać. Istnieją dwa sposoby na wejście w tryb zapisu/odczytu: EnterReadLock próbuje wejść w stan blokady w trybie zapisu. TryEnterReadLock(Int32) próbuje wejść w stan blokady w trybie odczytu, z opcją wyboru limitu czasu całkowitoliczbowego. EnterWriteLock próbuje wejść w stan Write Mode Lock. TryEnterWriteLock(Int32) próbuje wejść w stan blokady trybu zapisu, a czas przekroczenia czasu można wybrać. Istnieją dwa sposoby wyjścia z trybu zapisu/odczytu: ExitReadLock zmniejsza liczbę rekurencji trybu odczytu i wychodzi z trybu odczytu, gdy liczba wynosi 0 (zero). ExitWriteLock zmniejsza rekurencyjną liczbę wzorców zapisu i kończy tryb zapisu, gdy uzyskana liczba wynosi 0 (zero). Oto jak go używać:
Widać, że wątek 3 i 4 mogą wchodzić w tryb odczytu jednocześnie, natomiast wątek 5 może przejść w tryb zapisu po 5 sekundach (czyli po opuszczeniu blokady odczytu przez wątki 3 i 4). Zmodyfikuj powyższy kod, najpierw otwórz 2 wątki w trybie zapisu, a następnie otwórz wątki w trybie odczytu, a kod wygląda następująco:
Wyniki są następujące:
Jak widać, wątek 3 i 4 wchodzą w tryb zapisu, ale wątek 3 zajmuje blokadę zapisu jako pierwszej, więc wątek 4 musi czekać 10 sekund przed wejściem. Wątki 5 i 6 muszą zajmować blokadę odczytu, więc poczekajcie, aż wątek 4 opuści blokadę zapisu, zanim kontynuowamy. TryEnterReadLock i TryEnterWriteLock mogą ustawić timeout, uruchamiając to zdanie, wątek zablokuje tutaj, jeśli blokada może być zajęta w tym czasie, wtedy zwróć true, jeśli timeout jeszcze nie zajął blokady, wtedy zwróć false, rezygnuj z zajęcia blokady i kontynuuj wykonywanie następującego kodu bezpośrednio. EnterUpgradeableReadLock Klasa ReaderWriterLockSlim zapewnia tryb odczytu z możliwością ulepszenia, który różni się od trybu odczytu tym, że można ją również zaktualizować do trybu zapisu, wywołując metody EnterWriteLock lub TryEnterWriteLock. Ponieważ tylko jeden wątek może być w trybie ulepszania w danym momencie. Wątek, który wchodzi w tryb ulepszania, nie wpłynie na wątek w trybie odczytu, czyli gdy wątek przejdzie w tryb ulepszania, dowolna liczba wątków może jednocześnie przejść w tryb odczytu bez blokowania. Jeśli wiele wątków już czeka na uzyskanie blokady zapisu, uruchomienie EnterUpgradeableReadLock zablokuje do momentu wygaśnięcia czasu lub wyjścia z blokady zapisu. Poniższy kod pokazuje, jak zaktualizować system do blokady zapisu w trybie rozbudowywalnego odczytu.
Wpływ blokad odczytu/zapisu na wydajność jest oczywisty. Następujący kod testowy:
Powyższy kod symuluje działanie 500 zadań, z których każde zajmuje wątek puli wątków, z czego 20 to wątki zapisu, a symulowane 480 wątków odczytu. Odczyt danych zajmuje 10 ms, a zapis 100 ms, aby przetestować odpowiednio metodę blokady i metodę ReaderWriterLockSlim. Można oszacować, że dla ReaderWriterLockSlim można oszacować, zakładając, że 480 wątków czytanych jednocześnie, to zużywa 10ms, 20 operacji zapisu zajmuje 2000ms, więc czas zużywany wynosi 2010ms, a dla zwykłej metody blokady, ponieważ wszystkie są wyłączne, więc 480 operacji odczytu zajmuje 4800ms + 20 operacji zapisu, 2000ms = 6800ms. Wyniki wykazały zauważalną poprawę wydajności.
|