Pisząc(powoli bo powoli, ale pisząc) rozgrywkę sieciową do Kingdoms Clash.NET, odkładałem „na potem” synchronizację dostępu do danych. Nie używam wielu wątków – ot 2 per aplikacja – ale problem dał się we znaki. W końcu stwierdziłem, że nie ma co się bawić w odkładanie tego i trzeba to napisać. Jak postanowiłem tak zrobiłem i wynik mnie zadowala.
Jak już powiedziałem – nie używam wielu wątków. I w serwerze i w kliencie gry jeden wątek odbiera dane „z zewnątrz” a drugi je przetwarza. Nie ma wielu miejsc, gdzie oba wątki chcą uzyskać dostęp do tych samych danych(tak właściwie to tylko dwie kolekcje), ale mimo wszystko dość szybko objawił się ten problem. Dość długo udawało mi się go obchodzić bez używania jakiejkolwiek synchronizacji, ale mimo wszystko wiele to nie dało.
Jako iż „pomostem” przekazującym dane między wątkami była zwykła lista, wystarczyło opakować listę z biblioteki standardowej. Po krótkim(nawet bardzo krótkim) poszukiwaniu jak to można zrobić natrafiłem na rozwiązanie wykorzystujące sekcje krytyczne(lock + Monitor). Problemem jest dostęp read-only, który wiele wątków może mieć w tym samym czasie. Natrafiłem też na klasę ReaderWriterLockSlim, która umożliwia taki dostęp – wiele wątków może czytać, ale tylko jeden dostaje dostęp do zapisu. Dodatkowo udostępnia tryb upgradeable read, w trakcie którego możemy przejść w tryb zapisu. Klasa ta jest bardzo dobrze opisana na MSDN, więc nie będę się rozwijał.
Po kilku szybkich testach postanowiłem napisać własną implementację listy oraz czystego ICollection, gdyż często zdarza mi się używać tego interfejsu zamiast IList. Największym „problemem” było napisanie odpowiedniej implementacji IEnumerator – po wstępnie napisanej implementacji gra/serwer potrafiły mi się wykraczać na pętli foreach z InvalidOperationException „kolekcja została zmodyfikowana”. Zrobiłem błąd i pobierałem oryginalny numerator PRZED zdobyciem dostępu do całej kolekcji.
Dodatkowo napisałem dwie klasy, których zadaniem jest zabezpieczanie w ten sam sposób już istniejących kolekcji(lub dobierania się do numeratora w trybie upgradeable), lecz one nie były prawie w ogóle testowane.
Jest to też chyba pierwsza „paczka” kodu, której nie „wstydzę” się pokazać. Bardziej zostawiam ją dla siebie niż dla innych, lecz jeśli ktoś by chciał skorzystać – nie ma problemu. Licencja: MIT.
Download: Safe Collections
Programowanie wielowątkowe, czyli zabezpieczaj się od razu | Fiołek – blog…
Dziękujemy za publikację – Trackback z dotnetomaniak.pl…
Nie wiem jak wygląda twoja aplikacja ale czy nie da się w niej wykorzystać czegoś z System.Collections.Concurrent:
http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx
Za przemawiają: duże bezpieczeństwo, duża szybkość. BTW ICollection ma SyncRoot Property:
http://msdn.microsoft.com/en-us/library/bb356596.aspx dzięki którym można synchronizować użycie kolekcji.
Link do zip’a nie działa.
Przyznam, że kompletnie zapomniałem o System.Collections.Concurrent, lecz teraz przeglądając je widzę pewien problem – moje kolekcje dziedziczą z ICollection<>, one tylko z niegenerycznej wersji(a teraz już mi się zmieniać nie chce, nie jest to bardzo newralgiczny punkt, więc… Dodatkowo nie sądzę, by były o wiele wydajniejsze – ReaderWriterLockSlim wydaje się być szybki
).
Tyle razy przeglądałem interfejs ICollection<>, tyle razy go implementowałem, nigdy nie wpadłem, by przejrzeć wersję niegeneryczną. Długa droga przede mną… Dodatkowo
Dzięki, poprawione.