Zaczął się nowy rok akademicki i dostałem zastrzyk informacji. Za nim podążył strzał ze strzelby zadaniami domowymi. Takie życie studenta. W każdym razie, wspomiany w tytule wyścig o dostęp to problem, który opiewa przedmiot Programowanie Współbieżne. O co tu chodzi?
Odrobinka historii
Na początku komputery były proste i wykonywały wszystkie zadania liniowo. Czyli przychodził człowiek z kartą perforowaną, wkładał, komputer to przerabiał i coś wypluwał. Potem przeszliśmy z kart na cyfrowe nośniki pamięci, ale idea była taka sama. Uruchamiając program, był on ładowany do pamięci i wykonywany liniowo do końca, a potem kolejny program był uruchamiany. Ale ze wzrostem szybkości komputerów, powstał pomysł stworzenia pozornej wielozadaniowości kopmputera. Procesory nie były jeszcze w stanie wykonywać dwóch operacji na raz, ale można było przełączać się między procesami, raz ten, raz tamten, tak aby użytkownikowi się wydawało, że kilka rzeczy wykonuje się na raz.
No i super, wszystko śmiga, użytkownik też zadowolony, więc w czym problem? Otóż gdyby każdy program pracował na swoim kawałku pamięci i nie dotykał innych to nie byłoby problemu. Ale zdarza się, że do jakiegoś zasobu dostęp chcą uzyskać dwa programy na raz i go zmodyfikować. Pytanie jest proste: co się wydarzy?
Spójrzmy na prosty przykład:
static int y = 0; //zmienna globalna
void run()
{
for(int i = 0; i < 10; i++)
{
int x = y;
x++;
y = x;
}
}
Mamy teraz dwa wątki wykonujące metodę run()
uruchomione jednocześnie. Jeśli zostanie najpierw wykonany kod z pierwszego wątka, a potem z drugiego to y
wyniesie 20
, jeśli mamy wielordzeniowy procesor i oba wątki wykonają się jednocześnie (dla każdej instrukcji), to y
wyniesie 10
. A co jeśli po przypisaniu int x
wątek zostanie zatrzymany, a drugi nie? Wtedy wynik będzie inny. Patrząc na możliwie przypadki brzegowe dojdziemy do wniosku, że y >= 2 && y <= 20
. Tak czy siak, program jest niedeterministyczny, a to jest nie fajnie, bo jeśli kod raz działa, a raz nie to jest to zły kod.
Dlatego zostały stworzone różne mechanizmy kontroli. Monitor jest to system dający dostęp tylko jednemu wątkowi na raz. W C# uproszczoną składnią na używanie Monitora jest lock
. Ustalamy jakiś obiekt i na nim blokujemy dostęp. Jeśli dostęp jest już zablokowany to czekamy, aż zostanie odblokowany.
Poprawiony kod jest już deterministyczny i zawsze daje wynik y = 20
.
static int y = 0; //zmienna globalna
void run()
{
for(int i = 0; i < 10; i++)
{
lock(y)
{
int x = y;
x++;
y = x;
}
}
}
Innym systemem kontroli, bardziej nisko poziomowym jest Semafor, który oparty jest o licznik. Jeśli obiekt chce wejść do sekcji krytycznej (tej, w której wielodostęp psuje) wykonuje procedurę wejścia. Jeśli licznik jest dodatni to zostaje zmniejszony o 1 i obiekt dostaje dostęp, a jeśli licznik wynosi 0, to blokuje obiekt dopóki inny nie zwiększy licznika wykonując procedurę wyjścia.
Ciekawe porównanie Semaforów i Monitorów na Stack Overflow.
Mam nadzieję, że nieco przybliżyłem Ci, czytelniku, problem współbieżności.