Programowanie wspóªbie»ne

advertisement
1
Programowanie wspóªbie»ne
‚wiczenia 12 Przestrzenie krotek cz. 2
Zadanie 1: 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
s¡ identykowani unikalnymi liczbami z przedziaªu
0 . . . P − 1,
za± specjalno±ci liczbami z przedziaªu
Ka»dy pacjent w p¦tli niesko«czonej zaªatwia
zgªasza si¦ do lekarza.
0 . . . L − 1,
0 . . . S − 1.
P
pacjentów. Lekarze
pacjenci liczbami z przedziaªu
void wlasneZdroweSprawy(int pid),
po czym
Pacjent mo»e »yczy¢ sobie wizyty u konkretnego lekarza lub dowolnego
lekarza konkretnej specjalno±ci.
Ponadto pacjenci mog¡ by¢ uprzywilejowani b¡d¹ zwykli.
cjent oczekuje, a» lekarz go przyjmie, po czym odbywa si¦
identykatorem lekarza przyjmuj¡cego pacjenta.
Ka»dy lekarz natomiast w p¦tli niesko«czonej
void wizyta(int lid),
Pa-
gdzie lid jest
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 in-
formuje, 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
int pid ;
do {
sid ) {
/∗ identykatory lekarza i specjalno±ci ∗/
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) {
int lsid ;
bool czyLekarz, uprzyw;
do {
/∗ identykator pacjenta ∗/
wlasneZdroweSprawy(pid);
wybor(&czyLekarz, &lsid, &uprzyw);
if
(czyLekarz)
/∗ "dziura" na pozycji identykatora specjalno±ci ∗/
tsPut("ZGLOSZENIE
else
%d %d %d ?d",
pid, uprzyw, lsid);
/∗ "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
L > 0 liczb caªkowitych, to jest krotek w formacie "LICZBA %d".
1 < N ≤ L procesów, ka»dy z unikalnym identykatorem z przedziaªu 0 . . . N −
W przestrzeni krotek znajduje si¦
Do dyspozycji jest
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
Do dyspozycji jest N > 0 procesów, identykowanych unikalnymi liczbami z prze0 . . . 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. Nate krotki.
dziaªu
tomiast jakiekolwiek inne krotki wytworzone przez procesy mog¡ pozosta¢ w przestrzeni jedynie
je±li ich liczba jest staªa.
process zliczacz(int
int n = 0;
int v, c ;
id) {
/∗ zainicjalizuj obliczanie ∗/
if
(id == 0) {
tsPut("WYNIK
d %d",
0, 0);
3
}
/∗ policz lokalnie tyle krotek ile mo»esz ∗/
while (tsTryFetch("KROTKA
++n;
tsPut("TMP
%d",
?d",
&v)) {
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
++n;
tsPut("TMP
%d",
?d",
&v)) {
v);
}
/∗ globalna redukcja sumy lokalnych wyników ∗/
for (int skok = 1; id + skok < N; skok ∗= 2) {
if (id % (2 ∗ skok) != 0) break;
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
tsTryFetch, dopóki
true dla danego wzorca, i tak dalej dla kolejnych wygenerowanych wcze±niej wzorców
wygenerowanych krotek przy wykorzystaniu przez ka»dy proces instrukcji
zwraca ona
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) {
for (int skok = 1; id + skok < N; skok ∗= 2) {
if (id % (2 ∗ skok) != 0) break;
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
To rozwi¡zanie ma dwie wady.
wywoªaniu, krotka
"BARIERA 0"
0,
st¡d instrukcja wstrzymuj¡ca
tsRead.
Po pierwsze, b¦dzie ono dziaªaªo jedynie raz.
Przy drugim
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;
for (int skok = 1; id + skok < N; skok ∗= 2) {
if (id % (2 ∗ skok) != 0) break;
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.
5
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) {
for (int skok = 1; id + skok < N; skok ∗= 2) {
if (id % (2 ∗ skok) != 0) break;
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¦
zapewniona jest wªa±ciwo±¢ bariery. Je±li natomiast proces
0
tsPut
z fazy pierwszej w fazie drugiej wykonuje ostatni¡
tsFetch dla krotki wygenerowanej w fazie pierwszej, to wszystkie pozostaªe procesy
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.
instrukcj¦
wykonaªy
Innymi sªowy, po zako«czeniu funkcji przez proces
"BARIERA %d %d".
0
w przestrzeni nie zostanie »adna krotka
Download