1 Zadanie 1: Programowanie wspóªbie»ne wiczenia 12 Przestrzenie krotek cz. 2 Przychodnia lekarska W przychodni lekarskiej pracuje L > 0 lekarzy, z których ka»dy ma jedn z 0 < S L specjalno±ci, przy czym mo»e by wielu lekarzy tej samej specjalno±ci a dla danej specjalno±ci istnieje co najmniej jeden lekarz j posiadaj cy. Do przychodni lekarskiej zapisanych jest P pacjentów. Lekarze s identykowani unikalnymi liczbami z przedziaªu 0... L 1, pacjenci liczbami z przedziaªu 0... P 1, za± specjalno±ci liczbami z przedziaªu 0... S 1. Ka»dy pacjent w p tli niesko«czonej zaªatwia void wlasnezdrowesprawy(int pid), po czym zgªasza si do lekarza. Pacjent mo»e»yczy sobie wizyty u konkretnego lekarza lub dowolnego lekarza konkretnej specjalno±ci. Ponadto pacjenci mog by uprzywilejowani b d¹ zwykli. Pacjent oczekuje, a» lekarz go przyjmie, po czym odbywa si void wizyta(int lid), gdzie lid jest identykatorem lekarza przyjmuj cego pacjenta. Ka»dy lekarz natomiast w p tli niesko«czonej void pijekawe(int lid) a nast pnie przyjmuje pacjenta. W pierwszej kolejno±ci próbuje przyj pacjenta uprzywilejowanego, je±li tacy oczekuj na wizyt. Je±li nie ma czekaj cych pacjentów uprzywilejowanych, to lekarz przyjmuje dowolnego pacjenta (tj. zwykªego lub uprzywilejowanego) lub czeka, je±li nie ma»adnych pacjentów, których mógªby przyj. Przyj cie pacjenta to void badanie(int pid), gdzie pid jest identykatorem badanego pacjenta. W danej chwili lekarz mo»e bada co najwy»ej jednego pacjenta. Lekarz nie mo»e czeka, je±li oczekuje pacjent, którego mógªby przyj. Pacjenci uprzywilejowani mog zagªodzi zwykªych. Celem zadania jest zaimplementowanie procesów lekarza i pacjenta. Do wyboru lekarza przez pacjenta nale»y u»y funkcji void wybor(bool czylekarz, int id, bool uprzyw). Funkcja informuje, czy pacjent wybraª lekarza czy specjalizacj (czylekarz), jaki jest identykator tego/tej lekarza/specjalizacji (id) i czy pacjent czekaj c na wybran wizyt jest uprzywilejowany (uprzyw). process lekarz(int lid, int sid) { / identykatory lekarza i specjalno±ci / int pid; do { pijekawe(lid); if (! tstryfetch("zgloszenie?d %d %d %d", &pid, true, lid, sid)) tsfetch("zgloszenie?d?d %d %d", &pid, NULL, lid, sid); tsput("zaproszenie %d %d", pid, lid); badanie(pid); while (1); process pacjent(int pid) { / identykator pacjenta / int lsid ; bool czylekarz, uprzyw; do { wlasnezdrowesprawy(pid); wybor(&czylekarz, &lsid, &uprzyw); if (czylekarz) / "dziura" na pozycji identykatora specjalno±ci / tsput("zgloszenie %d %d %d?d", pid, uprzyw, lsid); else / "dziura" na pozycji identykatora lekarza / tsput("zgloszenie %d %d?d %d", pid, uprzyw, lsid); tsfetch("zaproszenie %d?d", pid, &lsid); wizyta( lsid );
2 while (1); W powy»szym rozwi zaniu nale»y zwróci uwag na dziurawe krotki. Dla przypomnienia, krotka z dziur na danej pozycji pasuje na tej pozycji do dowolnego wzorca. Zadanie 2: Porównywanie liczb W przestrzeni krotek znajduje si L > 0 liczb caªkowitych, to jest krotek w formacie "LICZBA %d". Do dyspozycji jest 1 < N L procesów, ka»dy z unikalnym identykatorem z przedziaªu 0... N 1. Ka»dy z procesów ma za zadanie wybra jedn z liczb (przy czym dwa procesy nie mog wybra tej samej liczby), porówna wybran liczb ze wszystkimi innymi L 1 liczbami, obliczaj c ile spo±ród nich jest wi kszych od wybranej liczby, a nast pnie umie±ci wynik oblicze«w przestrzeni krotek i zako«czy dziaªanie. Celem zadania jest napisanie tre±ci takiego procesu. Mo»na zaªo»y,»e po zako«czeniu oblicze«krotki z liczbami nie b d ju» potrzebne. process proc(int id) { int moja, tmp, wynik; tsfetch("liczba?d", &moja); tsput("tmp %d %d", id, moja); for (int i = id + N; i < L; i += N) { tsfetch("liczba?d", &tmp); tsput("tmp %d %d", i, tmp); wynik = 0; for (int i = 0; i < L; ++i) { tsread("tmp %d?d", i, &tmp); if (tmp > moja) ++wynik; tsput("wynik %d %d", moja, wynik); Kluczowym pomysªem w powy»szym rozwi zaniu jest zorganizowanie krotek z liczbami tak, aby mo»na byªo efektywnie po nich iterowa. Odbywa si to poprzez numerowanie krotek. Takie numerowanie mo»na zaimplementowa na wiele sposobów. Zaprezentowane rozwi zanie stara si zbalansowa prac poszczególnych procesów, zakªadaj c i» wszystkie one dziaªaj z podobn pr dko±ci. Zadanie 3: Zliczanie krotek W przestrzeni krotek znajduje si pewna liczba krotek w formacie "KROTKA %d", gdzie "%d" mo»e mie dowoln caªkowit warto±. Celem zadania jest napisanie wspóªbie»nego programu licz cego te krotki. Do dyspozycji jest N > 0 procesów, identykowanych unikalnymi liczbami z przedziaªu 0... N 1. W rezultacie swojego dziaªania ka»dy proces powinien wypisa liczb krotek "KROTKA %d" w przestrzeni i zako«czy dziaªanie. Nale»y przyj,»e po zako«czeniu ostatniego procesu wszystkie krotki "KROTKA %d" powinny nadal znajdowa si w przestrzeni krotek. Natomiast jakiekolwiek inne krotki wytworzone przez procesy mog pozosta w przestrzeni jedynie je±li ich liczba jest staªa. process zliczacz(int id) { int n = 0; int v, c; / zainicjalizuj obliczanie / if (id == 0) { tsput("wynik d %d", 0, 0);
3 / policz lokalnie tyle krotek ile mo»esz / while (tstryfetch("krotka?d", &v)) { ++n; tsput("tmp %d", v); / dodaj swój wynik do wyników reszty / tsfetch("wynik?d?d", &c, &v); tsput("wynik %d %d", c + 1, v + n); tsread("wynik %d?d", N, &n); / przywró oryginalne krotki / while (tstryfetch("tmp?d", &v)) tsput("krotka %d", v); printf ("WYNIK PROCESU %d = %d\n", id, n); W powy»szym rozwi zaniu, w liczeniu bior udziaª wszystkie procesy (w miar mo»liwo±ci). Najpierw wyci gaj one z przestrzeni krotek wszystkie krotki. Ka»dy proces liczy lokalnie, ile krotek udaªo mu si wyci gn, otrzymuj c lokalny wynik cz ±ciowy. Nast pnie te wyniki cz ±ciowe s sumowane dla wszystkich procesów, co daje sumaryczn liczb krotek w przestrzeni. Nale»y tak»e zwróci uwag na instrukcj tsread. Wstrzymuje ona wszystkie procesy do momentu, gdy wszystkie wyniki cz ±ciowe zostaªy dodane do wyniku globalnego. Sumowanie wyników cz ±ciowych w zaprezentowanym rozwi zaniu odbywa si w sekcji krytycznej proces najpierw wyci ga krotk z wynikiem globalnym z przestrzeni, dodaje do wyniku globalnego swój wynik lokalny, a nast pnie umieszcza zwi kszony wynik globalny ponownie w przestrzeni. Kolejne procesy modykuj wi c wynik globalny sekwencyjnie. Innymi sªowy, czas obliczania wyniku globalnego maj c dane wyniki lokalne jest proporcjonalny do liczby procesów, co mo»e by nieefektywne. Poni»sze rozwi zanie to poprawia. process zliczacz2(int id) { int n = 0; int v; / policz lokalnie tyle liczb ile mo»esz / while (tstryfetch("krotka?d", &v)) { ++n; tsput("tmp %d", v); / globalna redukcja sumy lokalnych wyników / tsfetch("wynik %d?d", id + skok, &v); n += v; tsput("wynik %d %d", id, n); / wczytaj globalny wynik / tsread("wynik %d?d", 0, &n); / przywró oryginalne liczby / while (tstryfetch("tmp?d", &v)) tsput("krotka %d", v); printf ("WYNIK PROCESU %d = %d\n", id, n); W ulepszonym rozwi zaniu sumowanie wyników cz ±ciowych odbywa si w czasie proporcjonalnym do logarytmu z liczby procesów. Ostateczny wynik globalny produkowany jest przez proces o indeksie 0, przez co zmienia si tak»e nieznacznie instrukcja wstrzymuj ca tsread. Warto tak»e
4 zauwa»y, i» w ulepszonym rozwi zaniu sumaryczna liczba instrukcji na krotkach "WYNIK %d %d %d" jest mniejsza jedynie o 1 od sumarycznej liczby instrukcji na tych krotkach w rozwi zaniu poprzednim. Efektywno± ulepszonego rozwi zania bierze si st d, i» u»ywa ono wielu takich krotek, przez co dost p do nich mo»e odbywa si wspóªbie»nie. Zadanie 4: Od±miecanie krotek (je±li starczy czasu) Celem zadania jest wykorzystanie poprzedniego rozwi zania do zaimplementowania efektywnej, samo-od±miecaj cej si bariery wykonywanej przez N > 0 procesów indeksowanych od 0 do N 1. Genez problemu jest fakt, i» niektóre programy mog pozostawia w przestrzeni krotki, które ju» nigdy nie zostan u»yte ±mieci. Dla przypomnienia, bariera to operacja wykonywana przez wszystkie N procesów w ten sposób, i»»aden proces nie mo»e zako«czy jej wykonywania o ile wszystkie procesy nie rozpocz ªy jej wykonywania. Maj c dan instrukcj bariery, od±miecanie nieu»ywanych krotek mo»e by zaimplementowane nast puj co po zako«czeniu oblicze«generuj cych takie krotki procesy wykonuj instrukcj bariery, po której nast puje od±miecanie wygenerowanych krotek przy wykorzystaniu przez ka»dy proces instrukcji tstryfetch, dopóki zwraca ona true dla danego wzorca, i tak dalej dla kolejnych wygenerowanych wcze±niej wzorców krotek. Mamy wtedy gwarancj,»e od±miecane krotki nie b d ju» potrzebne. Baz dla bariery mo»e by poprzednie rozwi zanie, jak poni»ej. void bariera(int id) { tsfetch("bariera %d", id + skok); tsput("bariera %d", id); tsread("bariera %d", 0); Rozwi zanie opiera si na hierarchicznym, logarytmicznym czekaniu a» wszystkie procesy dotr do bariery. Jako ostatni dociera proces 0, st d instrukcja wstrzymuj ca tsread. To rozwi zanie ma dwie wady. Po pierwsze, b dzie ono dziaªaªo jedynie raz. Przy drugim wywoªaniu, krotka "BARIERA 0" b dzie ju» w przestrzeni, wi c instrukcja wstrzymuj ca si nie zablokuje. Po drugie, usuni cie tej krotki z przestrzeni nie jest takie proste. Dokªadniej, musimy mie gwarancj,»e ju»»aden inny proces nie b dzie jej potrzebowaª. To sugeruje, i» powinni±my mie kolejn barier. Poprawione rozwi zanie rozwi zuje te dwa problemy nast puj co. Po pierwszy, bariery maj swoje numery (epoki) ka»de wywoªanie funkcji bariery zwi ksza numer epoki. Po drugie, kolejne wywoªanie bariery usuwa (w odpowiednim momencie) krotki bariery z poprzedniej epoki. void bariera2(int id) { static int epoka = 0; tsfetch("bariera %d %d", epoka, id + skok); tsput("bariera %d %d", epoka, id); tsread("bariera %d %d", epoka, 0); if (id == 0) tstryfetch("bariera %d %d", epoka 1, 0); W tym rozwi zaniu, funkcja bariery mo»e by woªana wielokrotnie. Po ka»dym jej zako«czeniu przez wszystkie procesy, w przestrzeni krotek pozostanie dokªadnie jedna krotka "BARIERA %d %d" liczba nieod±mieconych krotek bariery b dzie staªa, niezale»nie od tego, ile razy funkcja bariery byªa poprzednio woªana. Niemniej jednak takie rozwi zanie mo»e by nadal niezadowalaj ce.
Poni»sze rozwi zanie eliminuje t wad, zwi kszaj c dwukrotnie (ale nie asymptotycznie) zªo-»ono± operacji bariery. void bariera3(int id) { for (int faza = 1; faza <= 2; ++faza) { tsfetch("bariera %d %d", faza, id + skok); if (faza == 1) { tsput("bariera %d %d", faza, id); tsread("bariera %d %d", faza, 0); else { if (id!= 0) tsput("bariera %d %d", faza, id); else tsfetch("bariera %d %d", faza 1, 0); Rozwi zanie opiera si na dwóch fazach. Je±li jakikolwiek proces rozpoczyna faz drug, to wszystkie procesy, wª czaj c w to proces 0, wykonaªy ju» instrukcj tsput z fazy pierwszej zapewniona jest wªa±ciwo± bariery. Je±li natomiast proces 0 w fazie drugiej wykonuje ostatni instrukcj tsfetch dla krotki wygenerowanej w fazie pierwszej, to wszystkie pozostaªe procesy wykonaªy tsput z fazy drugiej, zako«czyªy wi c faz pierwsz, a zatem usuwana przez proces 0 krotka z fazy pierwszej nie b dzie ju» potrzebna»adnemu procesowi do tsread z fazy pierwszej. Innymi sªowy, po zako«czeniu funkcji przez proces 0 w przestrzeni nie zostanie»adna krotka "BARIERA %d %d". 5