Deweloperzy programujący wielowątkowo zapewne znają klasyczne typy wykorzystywanych przy okazji obiektów. Są to na przykład semafory, sekcje krytyczne (zwane też semaforami binarnymi) czy zdarzenia (events). Wszystkie one służą oczywiście do synchronizacji wątków tak, aby wykluczyć jednoczesny, wykluczający się dostęp do jednego zasobu.
Tego typu obiekty są wykorzystywane jednak głównie wtedy, kiedy mechanizm wątków jest zrealizowany w sposób specyficzny dla systemu operacyjnego – jak choćby poprzez API z Windows lub bibliotekę pthreads z Linuxa. Jeśli jednak mamy szczęście pracować z językiem, którego wielowątkowość jest częścią, wówczas korzysta się zwykle z nieco innych technik.
Taka sytuacja jest na przykład w Javie. Tam każdy obiekt (czyli instancja klasy dziedziczącej z java.lang.Object
) może być użyty jako obiekt synchronizujący. Z grubsza działa to w ten sposób, że gdy jeden z wątków zadeklaruje wykorzystanie danego obiektu – przy pomocy słowa kluczowego synchronized
– pozostałe nie mogą zrobić tego samego. Taka synchronizacja może odbywać się na wiele (składniowych) sposobów, jak choćby zadeklarowanie całej metody jak synchronizowanej:
W tym prościutkim przykładzie mamy zagwarantowane, że żaden postronny wątek nie wtrąci się w operację inkrementacji ze zwróceniem wartości (która nie jest atomowa) i stan licznika będzie zawsze poprawny.
Tak więc mamy semafory tudzież sekcje krytyczne. A co np. ze zdarzeniami (sygnałami)? Otóż każdy obiekt posiada metody wait
i notify
, umożliwiające czekanie na powiadomienie z innego wątku i oczywiście wysłanie takiego powiadomienia. Całkiem skuteczne i dosyć proste; naturalnie na tyle, na ile proste może być programowanie wielowątkowe :)
Ale czy oryginalne? Otóż dziwnym trafem na platformie .NET cała sprawa wygląda niemal dokładnie tak samo :) Odwzorowania przytoczonych elementów Javy w C# to odpowiednio: lock
(z dokładnością do kilku niuansów), Monitor.Wait
i Monitor.Pulse
. Sam sposób tworzenia wątków jest zresztą też bardzo bardzo podobny.
Wszelka zbieżność przypadkowa? Zdecydowanie nie. Lecz dobre rozwiązania warto jest przecież rozpowszechniać :]
To nie jest zbieżność tylko dlatego, że MS podprowadził rozwiązania z Javy, ale też dlatego, że monitor to powszechnie znany idiom mający swoje podwaliny w teorii programowania równoległego.
Ciekawe jest też, że Windows API ma inne prymitywy do wielowątkowości niż Linux pthreads. Ten pierwszy ma sekcje krytyczne / muteksy i eventy (plus różne inne), ten drugi muteksy i zmienne warunkowe (conditional variables). Ja w swoim CommonLib zrobiłem jedne za pomocą drugich, tak że każde są dostępne na obydwu systemach :)
Monitor to faktycznie zen wielowątkowości, ale ma tę wadę, że bez wsparcia od strony samego języka chyba niemożliwe jest jego zaimplementowanie. Dlatego część programistów może być przyzwyczajona do tych bardziej ‘klasycznych’ mechanizmów synchronizacji i przestawianie się na monitory może być nieco trudne. Nie zaszkodziłoby więc, że by sekcje krytyczne itp. były też w Javie czy .NET (chociaż oczywiście można je samemu napisać przy użyciu monitora).
A co do tych różnic, to Windows API ma chyba lekki przerost formy nad treścią :) pthreads jest nieco skromniejszy, chociaż przypominam sobie, że ma też zwykłe (niebinarne) semafory. Jak zresztą jednak zauważyłeś, jedne obiekty można zaimplementować za pomocą drugich.