Proces - Aragorn

Transkrypt

Proces - Aragorn
Systemy operacyjne
Studia podyplomowe 2015-2016
Wydział Informatyki PB
dr inż. Marcin Czajkowski
materiały przygotowane przez dr inż. Wojciecha Kwedlo
Procesy i wątki
Pojęcie procesu
●
Program = plik wykonywalny na dysku
●
Proces = uruchomiony i wykonywany program w pamięci
●
Program jest pojęciem statycznym.
●
Proces ma naturę dynamiczną (zmieniającą się). Zmianie ulegają m.in.
●
–
Licznik rozkazów (adres ostatnio wykonywanej instrukcji)
–
Rejestry procesora
–
Wskaźnik stosu
Proces ma przestrzeń adresową
–
Kod
–
Dane zainicjalizowane
–
Dane niezainicjalizowane
–
Stos
Uruchom
program
Model procesów
Pojedynczy
licznik rozkazów
(punkt widzenia
procesora)
A
B
Wiele liczników rozkazów
(punkt widzenia systemu oper.)
A
C
B
C
●
D
●
B
D
D
C
B
A
Czas
Z punktu widzenie procesora na
komputerze jest wykonywany
jeden program
Z punktu widzenia systemu
jednocześnie jest wykonywanych
wiele programów.
Stany procesu
Nowy
●
Nowy – proces został utworzony.
●
Gotowy – proces czeka na przydział procesora.
–
1
2
Gotowy
●
5
Oczekujący
4
7
7
Aktywny
Aktywny – wykonywane są instrukcje procesu.
–
3
●
W systemie z jednym procesorem w danej chwili
jeden proces może być aktywny
Oczekujący (uśpiony) – proces czeka na
zdarzenie (np. Zakończenie operacji we-wy).
–
Na poprzednim wykładzie proces, który
zainicjalizował transmisję DMA lub wysłała znak
do drukarki, był usypiany w oczekiwaniu na
przerwanie.
–
Proces w stanie uśpionym nie otrzyma procesora.
6
Zakończony
●
Proces mógłby się wykonywać, ale nie wykonuje
się, ponieważ w tej chwili wykonuje się jakiś
inny proces.
Zakończony – proces zakończył działanie
Stany procesu
Nowy
1
2
Gotowy
5
3
Oczekujący
4
7
7
Zakończony
Aktywny
6
Stany procesu
Nowy
●
Nowy – proces został utworzony.
●
Gotowy – proces czeka na przydział procesora.
–
1
2
Gotowy
●
5
Oczekujący
4
7
7
Aktywny
Aktywny – wykonywane są instrukcje procesu.
–
3
●
W systemie z jednym procesorem w danej chwili
jeden proces może być aktywny
Oczekujący (uśpiony) – proces czeka na
zdarzenie (np. Zakończenie operacji we-wy).
–
Na poprzednim wykładzie proces, który
zainicjalizował transmisję DMA lub wysłała znak
do drukarki, był usypiany w oczekiwaniu na
przerwanie.
–
Proces w stanie uśpionym nie otrzyma procesora.
6
Zakończony
●
Proces mógłby się wykonywać, ale nie wykonuje
się, ponieważ w tej chwili wykonuje się jakiś
inny proces.
Zakończony – proces zakończył działanie
Stany procesu
Nowy
●
Nowy – proces został utworzony.
●
Gotowy – proces czeka na przydział procesora.
–
1
2
Gotowy
●
5
Oczekujący
4
7
7
Aktywny
Aktywny – wykonywane są instrukcje procesu.
–
3
●
W systemie z jednym procesorem w danej chwili
jeden proces może być aktywny
Oczekujący (uśpiony) – proces czeka na
zdarzenie (np. Zakończenie operacji we-wy).
–
Na poprzednim wykładzie proces, który
zainicjalizował transmisję DMA lub wysłała znak
do drukarki, był usypiany w oczekiwaniu na
przerwanie.
–
Proces w stanie uśpionym nie otrzyma procesora.
6
Zakończony
●
Proces mógłby się wykonywać, ale nie wykonuje
się, ponieważ w tej chwili wykonuje się jakiś
inny proces.
Zakończony – proces zakończył działanie
Przejścia pomiędzy stanami procesu
●
1 (Nowy => Gotowy). Nowo utworzony
proces przechodzi do kolejki procesów
gotowych.
–
●
●
2 (Gotowy => Aktywny) Proces otrzymuje
przydział procesora.
3 (Aktywny => Gotowy) Procesowi został
odebrany procesor (i przekazany innemu
procesowi).
–
●
Planista długoterminowy (ang. long-term
scheduler) w systemach wsadowych.
Przejściami 2 oraz 3 zarządza planista
krótkoterminowy (ang. short-term).
4 (Aktywny => Oczekujący) Proces
przechodzi w stan oczekiwania na zajście
zdarzenia. (np. na zakończenie transmisji
wejścia -wyjścia – patrz poprzedni
wykład)v
Przejścia pomiędzy stanami procesu
●
5 (Oczekujący => Gotowy) Zdarzenie
na które czekał proces nastąpiło
(przerwanie we/wy na poprzednim
wykładzie).
–
●
●
Przejścia 4 oraz 5 są wykonywane
przy przeprowadzeniu synchronicznej
operacji wejścia wyjścia (ale nie
tylko).
6 (Aktywny => Zakończony). Proces
zakończył pracę (np. funkcja exit w
Uniksach, błąd ochrony)
7 (Gotowy =>Zakończony oraz
Oczekujący => Zakończony). Proces
został zakończony przez inny proces
(np. funkcja kill w systemie Unix).
Dodatkowy stan – zawieszony (ang. suspended)
●
●
●
Proces oczekuje bardzo długo na operacje we-wy (np. polecenie login)
–
Przejście do stanu zawieszonego
–
Pamięć zajmowana przez proces podlega wymianie (ang. swapping) tzn. zapisaniu
na dysk do obszaru wymiany (swap area).
–
Zwolniona pamięć może być wykorzystana przez inne procesy.
–
Po zajściu zdarzenia proces ponownie wczytywany z obszaru wymiany
Inne przyczyny zawieszenie procesu.
–
Żądanie użytkownika
–
Brak pamięci w systemie
–
Proces co jakiś czas cyklicznie wykonuje jakąś czynność np. Sprawozdawczość
Przejściami do i z stanu zawieszenie zarządza planista średnioterminowy (ang.
medium-term scheduler)
Diagram przejść z uwzględnieniem stanu zawieszenia.
●
Planista długoterminowy
●
Planista krótkoterminowy – najważniejszy i występujący w każdym systemie.
–
●
Rozstrzyga problem: “Któremu procesowi w stanie gotowym mam przydzielić procesor”
Planista średnioterminowy
Blok kontrolny procesu
ang. Process Control Block - PCB
●
PCB służy do przechowywania informacji o procesie
istotnych z punktu widzenia systemu operacyjnego
–
Stan procesu
–
Identyfikator procesu
–
Licznik rozkazów
–
Rejestry procesora
–
Informacja o przydzielonej pamięci
–
Informacja o otwartych plikach
–
Informacja o połączeniach sieciowych
–
Informacja niezbędna do tworzenia systemowych
struktur danych. System operacyjny posługuje się
różnymi kolejkami procesów. Jeżeli kolejki są
implementowane jako listy z dowiązaniami, PCB może
zawierać dowiązanie (wskaźnik) do następnego
elementu w kolejce
Przełączenie kontekstu pomiędzy procesami
P1
P2
P3
P4
Jądro
Jądro
Jądro
Jądro
Przełączenie kontekstu (ang. context switch),
a przełączenie trybu (ang. mode switch)
●
●
Przełączenie
procesu.
kontekstu
to
zmiana
Przełączenie trybu zmiana trybu pracy
procesora (jądra <=> użytkownika)
Przełączenie kontekstu
●
●
●
●
W większości systemów (Uniksy,Windows) przyjęto model, w którym funkcje systemu
wykonują się w kontekście procesu użytkownika. W uproszczeniu model ten zakłada
że system operacyjny jest kolekcją procedur wywoływanych przez procesy w celu
wykonania pewnych usług.
Stąd mówiłem o procesie wykonującym się w trybie jądra (jeżeli wykonywany jest kod
jądra).
Przejście od programu użytkownika do programu jądra w wyniku wywołania funkcji
systemowej (przerwanie programowe) lub przerwania sprzętowego wiąże się z
przełączeniem trybu.
Zmiana trybu jest znacznie mniej kosztowna niż zmiana kontekstu.
Utworzenie procesu
●
●
●
●
Proces rodzicielski tworzy proces potomny, który z kolei może stworzyć
kolejne procesy. Powstaje drzewo procesów.
Współdzielenie zasobów. Procesy rodzicielski i potomny mogą
–
Współdzielić część zasobów
–
Współdzielić wszystkie zasoby
–
Nie współdzielić żadnych zasobów
Wykonywanie
–
Procesy rodzicielski i potomny wykonują się współbieżnie
–
Proces rodzicielski oczekuje na zakończenie procesu potomnego.
Przestrzeń adresowa
–
Odrębna przestrzeń adresowa dla procesu potomnego
●
●
–
(fork w systemie Unix – proces potomny wykonuje się w nowej przestrzeni adresowej
będącej kopią przestrzeni procesu rodzicielskiego)
Proces ma nowy program załadowany do nowej przestrzeni adresowej (CreateProcess w
Win32)
Proces potomny i rodzicielski wykonują się w tej samej przestrzeni adresowej
(clone w Linuksie; wątki Java i POSIX)
Wywołania systemowe fork, exec, wait w Unix-ie
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int pid;
pid = fork();
if (pid < 0) { /* Błąd !!! */
fprintf(stderr, "Fork Failed");
exit(-1);
}
else if (pid == 0) { /* proces potomny */
execlp("/bin/ls","ls",NULL);
}
else { /* proces rodzicielski */
wait(NULL);
printf("Child Complete");
exit(0);
}
}
●
●
fork tworzy nowy proces
–
Wywoływana
rodzicielskiego
–
Powracają z niej proces potomny i
rodzicielski
procesu
exec – zastępuje obraz bieżącego
procesu nowym programem.
–
●
z
proces wołający nigdy nie wraca z
exec (chyba że powstanie błąd)
wait – blokuje proces do momentu
zakończenia procesu potomnego
Zakończenie procesu
●
Zakończenie na własne żądanie. Proces sam podejmuje decyzję o zakończeniu
pracy – wywołując odpowiednie wywołanie systemowe. (system Unix: exit ).
–
●
●
W programie w języku C jest to robione automatycznie po zakończeniu funkcji
main
Proces został zakończony w wyniku akcji innego procesu
–
Unix: proces otrzymał sygnał SIGKILL.
–
Polecenie kill w shellu, funkcje systemowe raise oraz kill.
Proces został zakończony przez system operacyjny
–
Naruszenie mechanizmów ochrony.
–
Przekroczenie ograniczeń na przyznany czas procesora.
–
Proces rodzicielski się zakończył (w niektórych systemach)
●
Cascading termination
Wielowątkowość
●
Jeden proces wykonuje się w wielu współbieżnych wątkach (ang. thread).
●
Każdy wątek (inna nazwa: proces lekki, ang. lightweight)
–
Ma swój własny stan (Aktywny, Gotowy, Zablokowany, ... )
–
Ma swoje wartości rejestrów i licznika rozkazów.
–
Ma swój własny stos (zmienne lokalne funkcji !!!).
–
Ma dostęp do przestrzeni adresowej, plików i innych zasobów procesu
●
●
WSZYSTKIE WĄTKI TO WSPÓŁDZIELĄ !!!
Operacje zakończenia, zawieszenia procesu dotyczą wszystkich wątków.
Procesy są od siebie izolowane, wątki nie !
Procesy i wątki
Proces
Przestrzeń adresowa
Otwarte pliki
Procesy potomne
Obsługa sygnałów
Sprawozdawczość
Zmienne globalne
Wątek 1
Wątek 2
Wątek 3
Licznik rozkazów
Rejestry
Stos i wskaźnik stosu
Stan
Licznik rozkazów
Rejestry
Stos i wskaźnik stosu
Stan
Licznik rozkazów
Rejestry
Stos i wskaźnik stosu
Stan
Proces z jednym wątkiem
●
Standardowy Unix
●
MS-DOS
Proces z wieloma wątkami
●
Linux
●
MS-Windows
●
POSIX
●
OS/2
●
Solaris
Cechy wątków
●
●
Zalety
–
Utworzenie i zakończenie wątku zajmuje znacznie mniej czasu niż w przypadku
procesu
–
Możliwość szybkiego przełączania kontekstu pomiędzy wątkami tego samego
procesu
–
Możliwość komunikacji wątków bez pośrednictwa systemu operacyjnego
–
Możliwość wykorzystania maszyn wieloprocesorowych SMP
Wady
–
“Źle zachowujący się wątek” może zakłócić pracę innych wątków tego samego
procesu.
●
W przypadku dwóch procesów o odrębnych przestrzeniach adresowych nie jest to
możliwe
Wątki na poziomie użytkownika
ang. user-level threads
●
●
●
System operacyjny nie jest świadom istnienia
wątków.
Zarządzanie wątkami jest przeprowadzane przez
bibliotekę w przestrzeni użytkownika.
–
Wątek A wywołuje funkcję read
–
Standardowo funkcja systemowa read jest
synchroniczna (usypia do momentu zakończenia
operacji)
–
Jednak “sprytna” implementacja w bibliotece
wywołuje wersję asynchroniczną i przełącza się do
wątku B.
Rozwiązanie to jest szybkie, ma jednak wady:
–
Dwa wątki nie mogą się wykonywać współbieżnie
na dwóch różnych procesorach.
–
Nie można odebrać procesora jednemu wątkowi i
przekazać drugiemu
Wątki na poziomie jądra
ang. kernel-level threads
●
Wątek jest jednostką systemu operacyjnego.
●
Wątki podlegają szeregowaniu przez jądro.
●
W systemie SMP wątki mogą się wykonywać
na różnych procesorach.
–
●
Przetwarzanie równoległe.
Windows i Linux wykorzystują tę metodę.
Przykład użycia wątków:
Program do przetwarzania obrazów
●
●
Użytkownik wydał polecenie “Save”
–
Jego wykonanie może potrwać kilkadziesiąt sekund.
–
Kto w tym czasie będzie obsługiwał mysz i klawiature ?
Wątek główny odpowiadający za interakcję z użytkownikiem.
–
Po wydaniu polecenia “Save” tworzy wątek roboczy zapisujący obraz do pliku.
–
Następnie powraca do konwersacji z użytkownikiem
–
Dzięki temu aplikacja nie jest zablokowana (klepsydra w Win)
zapisywania
●
Wątek roboczy wykonuje zapis i kończy pracę.
●
Uwaga na synchronizację wątków !!!
na czas
–
Co się stanie gdy po wydaniu polecenia “Save” natychmiast wydamy polecenie
“Usuń obraz”?
–
Sytuacja wyścigu (ang. race).
Problemy z wątkami
●
●
W standardowej bibliotece C, w wersji wielowątkowej, errno jest
implementowane jako prywatna zmienna globalna (nie współdzielona z innymi
wątkami)
Kilka wątków jednocześnie wywołuje funkcje malloc/free - może dojść do
uszkodzenia globalnych struktur danych (listy wolnych bloków pamięci)
–
Potrzeba synchronizacji => może prowadzić do spadku wydajności
Problemy z wątkami
●
●
Proces otrzymuje sygnał:
–
Wszystkie wątki otrzymują sygnał
–
Wybrany wątek otrzymuje sygnał
–
Wątek aktualnie aktywny otrzymuje sygnał
Proces wykonuje fork.
–
●
Proces wywołuje exit.
–
●
●
Czy duplikować jedynie działający wątek, czy też wszystkie wątki ?
Zakończyć proces czy też jedynie aktywny wątek ?
Anulowanie wątku (ang cancellation).
–
Wykonać natychmiast .
–
Wątek co jakiś czas sprawdza czy nie został anulowany.
Standard POSIX zawiera odpowiedzi na powyższe problemy.
POSIX threads – utworzenie i dołączenie wątku
●
void *thread(void *param) {
// tu kod wątku
// możemy przekazać wynik
return NULL
}
int main() {
pthread_t id;
// Parametr przekazywany wątkowi
void *param=NULL;
pthread_create(&id,NULL,&thread,param);
// Funkcja thread w odrębnym wątku
// współbieżnie z main
// id przechowuje identyfikator wątku
}
void *result;
// Czekaj na zakończenie wątku
pthread_join(id,&result);
// Wynik w result,zamiast &result można
// przekazać NULL
●
●
Funkcja pthread_create tworzy nowy
wątek. Rozpoczyna on pracę od
funkcji, której adres przekazano jako
trzeci argument.
Funkcja
pthread_join
usypia
wywołujący ją wątek do momentu,
kiedy
wątek
o
identyfikatorze
przekazanym jako pierwszy argument
zakończy pracę.
Zakończenie pracy wątku – powrót z
funkcji która go rozpoczyna.
Synchronizacja procesów (i wątków)
Potrzeba synchronizacji
●
Procesy wykonują się współbieżnie.
●
Jeżeli w 100% są izolowane od siebie, nie ma problemu.
●
Problem, jeżeli procesy komunikują się lub korzystają ze wspólnych zasobów.
–
●
Potrzeba utrzymywania wspólnych zasobów w spójnym stanie.
–
●
Przykład: Proces A przygotowuje wyniki, a proces B drukuje je na drukarce: Jak
zapewnić, aby B nie zaczął drukować przed zakończeniem przygotowania wyników przez
A.
Np. proces A dodaje element do listy (z dowiązaniami), a jednocześnie B przegląda listę,
która w momencie trwania operacji dodania ma stan niespójny
Potrzeba synchronizacji dotyczy także współbieżnych wątków.
–
W kolejnych slajdach będę używał – zgodnie z literaturą pojęcia “proces”, jednakże
wszystkie przykłady oparte są na założeniu, że procesy wykonują się współbieżnie.
Zatem bardziej odpowiednie byłoby użycie terminu wątki.
–
Np. dwa wątki wywołują funkcję malloc, która przydziela pamięć.
Problem producenta-konsumenta
(z ograniczonym buforem – ang. bounded buffer)
●
●
●
●
Jeden proces (producent) generuje (produkuje) dane a drugi (konsument) je pobiera
(konsumuje). Wiele zastosowań w praktyce np. drukowanie.
Jedno z rozwiązań opiera się na wykorzystaniu tablicy działającej jak bufor cykliczny, co
pozwala na zamortyzowanie chwilowych różnic w szybkości producenta i konsumenta. Tę
wersję problemu nazywa się problemem z ograniczonym buforem.
Problem: jak zsynchronizować pracę producenta i konsumenta – np. producent zapełnia
bufor, konsument usiłuje pobrać element z pustego bufora.
Dwie wersje: dla jednego producenta i konsumenta i wielu producentów i konsumentów.
out=3
0
1
2
3
4
counter=8
n=16
5
8
Stąd konsument pobierze
kolejny egzemplarz
6
7
9
in=11
10
11
12
13
Tu producent wstawi
kolejny egzemplarz
14
15
Producent konsument z buforem cyklicznym
Zmienne wspólne
const int n;
typedef … Item;
Item buffer[n];
int out=0;
int in = 0;
counter = 0;
●
Czeka, jeżeli counter==n, tzn. bufor pełny
Konsument pobiera element z bufora z pozycji out
–
●
// bufor
// indeks konsumenta
// indeks producenta
// liczba elementów w buforze
Producent umieszcza element w buforze na pozycji in
–
●
// rozmiar bufora
Czeka, jeżeli counter==0 tzn. bufor pusty.
Zmienne in oraz out zmieniane są zgodnie z regułą
–
i=(i+1)%n
–
Wartości kolejnych indeksów do tablicy buffer
●
Jeżeli i==n-1 to nowei=0
Rozwiązanie (prymitywne) dla jednego producenta
i konsumenta z wykorzystaniem aktywnego czekania
Counter += 1;
Counter -= 1;
Producent
Item pitm;
while (1) {
…
produce an item into pitm
…
while (counter == n)
;
buffer[in] = pitm;
in = (in+1) % n;
counter += 1;
}
●
Konsument
Item citm;
while (1) {
while (counter == 0)
;
citm = buffer[out];
out = (out+1) % n;
counter -= 1;
…
consume the item in citm
…
}
Dygresja: dlaczego rozwiązanie oczywiście niepoprawne dla więcej niż jednego
konsumenta albo producenta ?
●
Counter jest zmienną współdzieloną przez obydwa procesy.
●
Co się może stać gdy jednocześnie obydwa procesy spróbują ją zmienić ?
Gdzie tkwi problem?
●
●
Architektura RISC: ładuj do rejestru,
zwiększ wartość,zapisz wynik.
Niech x oznacza jest modyfikowaną
zmienną counter.
–
●
●
a) Producent
R1 <= x
R1 = R1+1
R1 => x
Rozważmy dwie możliwe kolejności
wykonywania
instrukcji
poszczególnych procesów.
a) Poprawna wartość 5.
–
b) Niepoprawna wartość 4.
Wybór jednej z tych
niedeterministyczny.
x=5
b) Producent
wielkości
x=5
R3 <= x
R3 = R3-1
R3 => x
Przyjmijmy, że x=5
–
Konsument
Konsument
R1 <= x
x=5
R3 <= x
R3 = R3-1
R1 = R1+1
Sytuacja wyścigu
(ang. race condition)
●
R1 => x
R3 => x
x=4!
Wyścigi - races
●
●
●
O warunku wyścigu mówimy gdy wynik zależy od kolejności wykonywania instrukcji
procesów. W poprawnym programie nie powinno być wyścigów. Innymi słowy
Uwaga: Ze względu na niedeterminizm (nigdy nie wiemy w jakiej kolejności wykonają
się procesy) do błędu może (ale nie musi dojść). W związku z tym przydatność
testowania do badanie poprawności programów współbieżnych jest mocno
ograniczona. Nic tu nie zastąpi analizy kodu (a często mniej lub bardziej formalnych
dowodów poprawności).
Typowe sytuacje: błąd objawia się przeciętnie raz na trzy miesiące nieprzerwanej pracy
programu.
●
●
W naszym przykładzie musimy zapewnić, aby jeden proces w danej chwili mógł
odwoływać się do zmiennej Counter. Innymi słowy dostęp do tej zmiennej powinien
znajdować się wewnątrz sekcji krytycznej.
Problem sekcji krytycznej
●
Chcemy, aby w jednej chwili w sekcji krytycznej mógł przebywać tylko jeden proces
●
Założenia
●
–
Proces na przemian przebywa w sekcji krytycznej albo wykonuje inne czynności
–
Proces przebywa w sekcji krytycznej przez skończony czas.
Rozwiązanie
–
Czynności wykonywane przy wejściu do sekcji - protokół wejścia
–
Czynności wykonywane przy wyjściu z sekcji - protokół wyjścia
A opuszcza
sekcję krytyczną
A wchodzi do
sekcji krytycznej
Proces A
B próbuje wejść do
sekcji krytycznej
Proces B
B wstrzymany
Czas
B wchodzi do
sekcji krytycznej
B opuszcza
sekcję krytyczną
Warunki dla rozwiązania sekcji krytycznej
●
Wzajemne wykluczanie.
–
●
Postęp
–
●
W danej chwili tylko jeden proces może być w sekcji krytycznej.
Proces który nie wykonuje sekcji krytycznej nie może blokować procesów
chcących wejść do sekcji.
Ograniczone czekanie
–
Proces nie może czekać na wejście do sekcji krytycznej w nieskończoność
Rozwiązania problemu sekcji krytycznej
●
●
Wyłącz przerwania
–
Nie działa w systemach wieloprocesorowych
–
Problematyczne w systemach z ochroną
–
Wykorzystywane do synchronizacji w obrębie jądra (zakładając jeden procesor)
Rozwiązania z czekaniem aktywnym
–
Algorytm Petersona dla dwóch procesów – wymaga trzech zmiennych !!!
–
Algorytm piekarni dla wielu procesów.
–
Rozwiązania wykorzystujące specjalne instrukcje maszynowe np. rozkaz zamiany:
●
XCHG rejestr, pamięć
●
Architektura systemu musi zapewniać atomowe wykonanie instrukcji.
●
W systemach wieloprocesorowych nie jest to trywialne
Przykład realizacji z wykorzystaniem instrukcji XCHG
●
Niech instrukcja XCHG (zmienna,wartość) nadaje zmiennej nową wartość i
jednocześnie zwraca starą. Zakładamy, że jest to instrukcja atomowa – nie może być
przerwana.
–
●
Na ogół wartość przechowywana jest w rejestrze. Implementacja tej instrukcji nie jest
trywialna – wymaga dwóch cykli dostępu do pamięci. Na szczęście to problem projektantów
sprzętu.
Możemy podać stosunkowo proste rozwiązanie problemu sekcji krytycznej.
int lock=0; // zmienna wykorzystana do synchronizacji
// lock==1 oznacza, że jakiś proces jest w sekcji krytycznej
void Process() {
while (1) {
// Wynik xchg równy jeden oznacza, że poprzednia wartość była równa 1
// zatem ktoś inny był w sekcji krytyczne
while(xchg(lock,1)==1); // Protokół wejścia
// Proces wykonuje swoją sekcję krytyczną, wiemy że lock==1
}
lock=0; // Protokół wyjścia
// Proces wykonuje pozostałe czynności
}
Dlaczego potrzebujemy XCHG ?
●
Ktoś mógłby zaproponować “ulepszenie” nie wymagające tej instrukcji.
while (lock==1); // czekamy, aż inni opuszczą sekcje krytyczną.
lock=1;
// wchodzimy do sekcji krytycznej i zabraniamy tego innym.
●
Niestety to “ulepszenie” jest niepoprawne - prowadzi do wyścigu. Dlaczego ?
Wskazówka: wiele się może zmienić pomiędzy wyjściem z pętli while a
przypisaniem zmiennej lock.
Czekanie aktywne
●
Marnowany jest czas procesora
–
●
●
Uzasadnione gdy:
–
Czas oczekiwania stosunkowo krótki (najlepiej krótszy od czasu przełączenia
kontekstu)
–
Liczba procesów @ Liczba procesorów
Przykład zastosowania jądro Linux-a w wersji SMP
–
●
Zmarnowany czas można by przeznaczyć na wykonanie innego procesu.
Funkcje typu spin_lock
Alternatywą do czekania aktywnego jest przejście procesu w stan zablokowany
–
Semafory
–
Monitory
Semafor zliczający
●
Zmienna całkowita S i trzy operacje: nadanie wartości początkowej, oraz Wait i Signal.
●
Definicja klasyczna (E. Dijkstra):
●
●
●
–
Wait (czekaj): while (S<=0) ;
S--
–
Signal(sygnalizuj): S++
–
Operacje Wait i Signal są operacjami atomowymi
Początkowa wartość S – liczba wywołań operacji Wait bez wstrzymywania.
Definicja klasyczna oparta jest na aktywnym czekaniu. W praktyce używa się innej
definicji opartej na usypianiu procesów (M. Ben-Ari) :
–
Wait: Jeżeli S>0, to S=S-1, w przeciwnym wypadku wstrzymaj (przełącz w stan oczekujący)
wykonywanie procesu – proces ten nazywany wstrzymanym przez semafor.
–
Signal: Jeżeli są procesy wstrzymane przez semafor, to obudź jeden z nich, w przeciwnym
wypadku S=S+1.
Implementacja według powyższej definicji m.in. w standardzie POSIX threads.
–
funkcje sem_init, sem_wait oraz sem_post (odpowiednik signal)
–
funkcja sem_trywait – nie wstrzymuje procesu, ale zwracająca kod błędu jeżeli proces byłby
wstrzymany.
Implementacja semafora
●
●
●
●
Semafor to: (a) Bieżąca wartość + (b) Lista (np.
FIFO) procesów oczekujących
Nieco zmodyfikowana (ale równoważna z
definicją Ben Ariego) implementacja –
zakładamy że wartość zmiennej może być
ujemna – wtedy przechowuje ona liczbę
wstrzymanych procesów.
Zakładamy dostępność
poziomie jądra systemu:
dwóch
–
Sleep:
realizuje
przejście
Aktywny=>Oczekujący
–
Wakeup: Oczekujący=>Gotowy
funkcji
na
procesu
Oczywiście Wait i Signal muszą być operacjami
atomowymi – ich wykonanie nie może być
przerwane przełączeniem kontekstu do innego
procesu.
class Semaphore {
int value;
ProcessList pl;
public:
Semaphore(int a) {value=a;}
void Wait ();
void Signal ();
};
Semaphore::Wait() ()
{
value -= 1;
if (value < 0) {
Add(this_process,pl)
Sleep (this_process);
}
}
Semaphore::Signal () {
value += 1;
if (value <= 0) {
Process P=Remove(P)
Wakeup (P);
}
}
Rozwiązanie sekcji problemu sekcji
krytycznej przy pomocy semaforów
Semaphore Sem(1);
void Process() {
while (1) {
Sem.Wait();
// Proces wykonuje swoją sekcję krytyczną
Sem.Signal()
// Proces wykonuje pozostałe czynności
}
}
●
●
●
Protokół wejścia i wyjścia są trywialne, ponieważ semafory zaprojektowano
jako narzędzie do rozwiązania problemu sekcji krytycznej
Zmodyfikujmy warunki zadania, tak że w sekcji krytycznej może przebywać
jednocześnie co najwyżej K procesów.
Pytanie. Co należy zmienić w programie ?
Zastosowanie semafora to zapewnienia określonej
kolejności wykonywania instrukcji procesów
●
Chcemy aby instrukcja A jednego procesu wykonała się po instrukcji B drugiego.
Używamy semafora S zainicjalizowanego na zero.
.
.
.
.
.
S.Wait();
A;
.
.
.
.
.
B;
S.Signal();
Problem producent-konsument z wykorzystaniem semaforów
const int n;
Semaphore empty(n),full(0),mutex(1);
Item buffer[n];
●
●
●
Producent
Konsument
int in = 0;
Item pitem;
while (1) {
// produce an item into pitem
empty.Wait();
mutex.Wait();
buffer[in] = pitem;
in = (in+1) % n;
mutex.Signal();
full.Signal();
}
int out = 0;
Item citem;
while (1) {
full.Wait();
mutex.Wait();
citem = buffer[out];
out = (out+1) % n;
mutex.Signal();
empty.Signal();
// consume item from citem
}
Semafor mutex zapewnia
współdzielonych.
wzajemne
wykluczanie
przy
dostępie
do
zmiennych
Semafor full zlicza liczbę elementów w buforze (pełnych miejsc w tablicy). Wstrzymuje
konsumenta gdy w buforze nie ma żadnego elementu.
Semafor empty zlicza liczby pustych miejsc w tablicy. Wstrzymuje producenta gdy w tablicy
nie ma wolnego miejsca..
Semafory binarne
●
Zmienna może przyjmować tylko wartość zero lub jeden
–
Operacje mają symbole WaitB, SingalB
–
Wartość jeden oznacza, że można wejść do semafora (wykonać WaitB)
–
Wartość zero oznacza że operacja WaitB wstrzyma proces.
●
Mogą być prostsze w implementacji od semaforów zliczających.
●
Implementacje
–
Mutexy w POSIX threads.
pthread_mutex_unlock).
(pthread_mutex_create,
–
W win32 mutexy noszą nazwę sekcji krytycznych
–
W Javie mutex jest związany z każdym obiektem
●
Słowo kluczowe synchronized.
●
Więcej o Javie przy omawianiu monitorów
phread_mutex_lock,
Blokada (Zakleszczenie, ang. deadlock)
●
●
Zbiór procesów jest w stanie blokady, kiedy każdy z nich czeka na zdarzenie,
które może zostać spowodowane wyłącznie przez jakiś inny proces z tego
zbioru.
Samochody nie mają wstecznego biegu = Brak wywłaszczeń zasobów
Przykład blokady
●
●
●
Sekwencja
blokady.
instrukcji
prowadząca
–
P0 wykonał operacje A.Wait()
–
P1 wykonał operacje B.Wait()
–
P0 usiłuje wykonać B.Wait()
–
P1 usiłuje wykonać A.Wait()
–
P0 czeka na zwolnienie B przez P1
–
P1 czeka na zwolnienie B przez P0
–
Będą czekały w nieskończoność !!!
do
Do blokady może (ale nie musi) dojść.
Pytanie: Jak w tej sytuacji zagwarantować
brak blokady ?
Semaphore A(1),B(1);
Proces P0
Proces P1
A.Wait();
B.Wait();
.
.
.
B.Signal();
A.Signal();
B.Wait();
A.Wait();
.
.
.
A.Signal();
B.Signal();
Opis formalny: graf alokacji zasobów
Okrąg oznacza proces, a prostokąt zasób.
semafor A
P0
P1
–
Strzałka od procesu do zasobu => proces czeka na
zwolnienie zasobu
–
Strzałka od zasobu do procesu => proces wszedł w
posiadanie zasobu.
semafor B
●
●
●
Stan blokady ma miejsce, wtedy i tylko wtedy gdy w grafie alokacji zasobów występuje
cykl.
Jedna z metod uniknięcia blokady => nie dopuszczaj do powstania cyklu. Np. każdy
proces wchodzi w posiadanie zasobów w określonym porządku (identycznym dla
wszystkich procesów).
W literaturze (Silberschatz i wsp.) opisano wersję z więcej niż jednym egzemplarzem
zasobu (np. drukarki)
Zagłodzenie (ang. starvation)
●
●
●
●
Proces czeka w nieskończoność, pomimo że zdarzenie na które czeka występuje. (Na
zdarzenie reagują inne procesy)
Przykład: Jednokierunkowe przejście dla pieszych, przez które w danej chwili może
przechodzić co najwyżej jedna osoba.
–
Osoby czekające na przejściu tworzą kolejkę.
–
Z kolejki wybierana jest zawsze najwyższa osoba
–
Bardzo niska osoba może czekać w nieskończoność.
Zamiast kolejki priorytetowej należy użyć kolejki FIFO (wybieramy tę osobę, która
zgłosiła się najwcześniej).
Inny przykład: z grupy procesów gotowych planista krótkoterminowy przydziela zawsze
procesor najpierw procesom profesorów a w dalszej kolejności procesom studentów.
–
Jeżeli w systemie jest wiele procesów profesorów, to w kolejce procesów gotowych znajdzie się
zawsze co najmniej jeden i proces studenta będzie czekał w nieskończoność na przydział
procesora.
Problem pięciu filozofów
●
Każdy filozof siedzi przed jednym talerzem
●
Każdy filozof na przemian myśli i je
●
Do jedzenia potrzebuje dwóch widelców
●
●
–
Widelec po lewej stronie talerza.
–
Widelec po prawej stronie talerza.
W danej chwili widelec może być posiadany tylko
przez jednego filozofa.
Zadanie: Podaj kod dla procesu i-tego filozofa
koordynujący korzystanie z widelców.
Problem czytelników i pisarzy
●
Modyfikacja problemu sekcji krytycznej.
●
Wprowadzamy dwie klasy procesów: czytelników i pisarzy.
●
Współdzielony obiekt nazywany jest czytelnią.
●
W danej chwili w czytelni może przebywać
●
–
Jeden proces pisarza i żaden czytelnik.
–
Dowolna liczba czytelników i żaden pisarz.
Rozwiązanie prymitywne: Potraktować czytelnię jak obiekt wymagający
wzajemnego wykluczania wszystkich typów procesów.
–
●
Prymitywne, ponieważ ma bardzo słabą wydajność. Jeżeli na wejście do czytelni
czeka wielu czytelników i żaden pisarz to możemy wpuścić od razu wszystkich
czytelników
W literaturze opisano rozwiązania:
–
Z możliwością zagłodzenia pisarzy
–
Z możliwością zagłodzenia czytelników
–
Poprawne
Problem śpiącego fryzjera
c
●
Jeden proces fryzjera i wiele procesów klientów.
●
Współdzielone zasoby: n krzeseł w poczekalni i jedno krzesło fryzjera
●
Napisz program koordynujący pracę fryzjera i klientów
Wady semaforów
●
●
Jeden z pierwszych mechanizmów synchronizacji
Generalnie jest to mechanizm bardzo niskiego poziomu - trochę odpowiadający
programowaniu w assemblerze.
●
Duża podatność na błędy, trudno wykazać poprawność programu
●
Przykład: Jeżeli zapomnimy o operacji signal, nastąpi blokada
●
Bardziej strukturalne mechanizmy synchronizacji
–
Regiony krytyczne
–
Monitory
Regiony krytyczne
●
Współdzielona zmienna v typu T jest deklarowana jako:
var v: shared T
●
Dostęp do zmiennej v wykonywany przy pomocy operacji
region v when B do S
●
●
●
B jest wyrażeniem logicznym
Tak długo, jak instrukcja S się wykonuje, żaden inny proces nie może się
odwołać do zmiennej v.
Jeżeli wyrażenie B nie jest spełnione, to proces jest wstrzymywany do
momentu jego spełnienia.
Przykład: producent-konsument z ograniczonym buforem
var buffer: shared record
●
pool: array [0..n–1] of item;
count,in,out: integer
end;
region buffer when count < n
do begin
●
Deklaracja
współdzielonej.
Wstawienie elementu
bufora. (Producent).
zmiennej
nextp
do
pool[in] := nextp;
in:= in+1 mod n;
count := count + 1;
end;
region buffer when count > 0
do begin
nextc := pool[out];
out := out+1 mod n;
count := count – 1;
end;
●
Usunięcie elementu nextc z bufora
(Konsument).
Idea monitora (a właściwie zmiennej warunkowej)
●
●
Udostępnienie procesom operacji pozwalającej procesowi wejść w stan uśpienia
(zablokowania) – wait oraz operacji signal pozwalającej na uśpienie obudzonego
procesu.
Ale tu natrafiamy na (stary) problem wyścigów, który ilustruje poniższy przykład:
if (Jeszcze_nie_bylo_zdarzenia_muszę_wykonać_wait)
wait() // to zaczekam
●
●
Co się stanie, jeżeli zdarzenie na które czeka proces zajdzie po instrukcji if, ale przed
uśpieniem procesu ? Proces zgubi zdarzenie (i być może nigdy się nie obudzi)
W takim razie wykonajmy cały ten kod wewnątrz sekcji krytycznej ?
–
●
Ale gdy proces wykona wait() - to przejdzie w stan uśpienia nie zwalniając sekcji
krytycznej. Przy próbie wejścia do sekcji przez inny proces na pewno dojdzie do
blokady.
Rozwiązanie: Atomowa operacja wait powodująca jednoczesne uśpienie procesu i
wyjście z sekcji krytycznej
Monitory
monitor mon {
int foo;
int bar;
public void proc1(…) {
}
public void proc2(…) {
}
};
●
●
●
Pseudokod przypominający definicję klasy C++.
Współdzielone zmienne foo oraz bar są dostępne wyłącznie z procedur
monitora.
Procesy synchronizują się wywołując procedury monitora (np. proc1 i proc2)
–
●
Tylko jeden proces (wątek) może w danej chwili przebywać w procedurze
monitora. Gwarantuje to automatycznie wzajemne wykluczanie.
Mówimy że proces “przebywa wewnątrz monitora”.
Zmienne warunkowe (ang. condition)
●
Problem: proces postanawia zaczekać wewnątrz monitora aż zajdzie zdarzenie
sygnalizowane przez inny proces.
–
●
Jeżeli proces po prostu zacznie czekać, nastąpi blokada, bo żaden inny proces nie
będzie mógł wejść do monitora i zasygnalizować zdarzenia.
Zmienne warunkowa (typu condition). Proces, będący wewnątrz monitora,
może wykonać na niej dwie operacje.
–
Niech deklaracja ma postać: Condition C;
–
C.wait() Zawiesza wykonanie procesu, i jednocześnie zwalnia monitor pozwalając
innym procesom wejść do monitora.
–
C.signal() Jeżeli nie ma procesów zawieszonych przez operację wait nic się nie
dzieje. W przeciwnym wypadku dokładnie jeden proces zawieszony przez operacje
wait zostanie wznowiony. (od następnej instrukcji po wait).
–
Możliwa jest trzecia operacja C.signallAll() wznawiająca wszystkie zawieszone
procesy.
Przykład 1: Implementacja semafora zliczającego przy pomocy
monitora
monitor Semafor {
int Licznik;
Condition NieZero;
// coś na kształt konstruktora
Semafor(int i) {
Licznik=i;
}
void wait() {
if (Licznik==0)
NieZero.wait();
Licznik=Licznik-1;
}
void signal() {
Licznik=Licznik+1;
NieZero.signal();
}
};
●
Deklaracja:
–
●
Semafor S(1);
Proces
potrzebujący
wzajemnego wykluczania.
S.wait()
// sekcja krytyczna
S.signal()
// pozostałe czynności
Semantyka Hoare'a i semantyka Mesa
●
●
●
●
●
Przypuśćmy że proces P wykonał operację wait i został zawieszony. Po jakimś
czasie proces Q wykonuje operacje signal odblokowując P.
Problem: który proces dalej kontynuuje pracę: P czy Q. Zgodnie z zasadą
działania monitora tylko jeden proces może kontynuować pracę.
Semantyka Mesa
–
Proces który wywołał operacje signal (Q) kontynuuje pierwszy.
–
P może wznowić działanie, gdy Q opuści monitor.
–
Wydaje się być zgodna z logiką, po co wstrzymywać proces który zgłosił
zdarzenie.
Semantyka Hoare'a
–
Proces odblokowany (P) kontynuuje jako pierwszy.
–
Może ułatwiać pisanie poprawnych programów. W przypadku semantyki Mesa nie
mamy gwarancji, że warunek, na jaki czekał P jest nadal spełniony (P powinien raz
jeszcze sprawdzić warunek).
Aby uniknąć problemów z semantyką najlepiej przyjąć że operacja signal jest
zawsze ostatnią operacją procedury monitora.
Struktura monitora
●
●
●
Zasada działania: W danej chwili w
procedurze monitora może przebywać
jeden proces.
Z każdą zmienną warunkową związana
jest kolejka procesów, które wywołały
wait i oczekują na zasygnalizowanie
operacji.
Po zasygnalizowaniu warunku proces
(który
wykonał
operację
signal)
przechodzi do kolejki urgent queue.
–
Zatem
implementacja
semantykę Hoare'a
realizuje
Przykład 2: Problem producent-konsument z buforem cyklicznym
monitor ProducentKonsument {
int Licznik=0,in=0,out=0;
Condition Pelny;
Condition Pusty;
int Bufor[N];
void Wstaw(int x) {
if (Licznik==n)
Pełny.wait();
Bufor[in]=x;
in=(in+1)%N;
Licznik++;
Pusty.signal();
}
●
●
●
●
●
int Pobierz() {
if (Licznik==0)
Pusty.wait();
int x=Bufor[out];
out=(out+1)%N;
Licznik=Licznik-1;
Pelny.signal();
return x;
}
};
●
Rozwiązanie dla wielu konsumentów i
wielu producentów.
Przyjmujemy,
że
w
buforze
są
przechowywane liczby całkowite (int).
Producent chcąc wstawić element do
bufora wywołuje procedurę monitora
Wstaw.
Konsument chcąc pobrać element z bufora
wywołuje Pobierz.
Gdy bufor jest pusty, to konsumenci są
wstrzymywani na zmiennej warunkowej
Pusty.
Gdy bufor jest pełny to producenci są
wstrzymywani na zmiennej warunkowej
Pełny.
Tworzenie wątku w Javie
●
class Worker extends Thread
{
public void run() {
Dwie metody:
–
Rozszerzenie klasy Thread
–
Implementacja interfejsu Runnable
System.out.println("Wątek roboczy");
}
}
●
public class First
Rozszerzenie klasy Thread
–
Metoda run jest wykonywana w
odrębnym wątku
–
Deklarujemy obiekt klasy
–
Metoda start() uruchamia wątek.
{
public static void main(String args[]) {
Worker runner = new Worker();
runner.start();
System.out.println("Wątek główny");
runner.join();
}
}
●
–
Reprezentowany przez obiekt klasy
Worker.
Metoda join() zawiesza aktualny wątek
do momentu zakończenia wątku
reprezentowanego przez obiekt klasy
Thread.
Metody synchronizowane w Javie
●
●
●
Z każdym obiektem w Javie związany jest zamek (ang. lock).
Metoda jest zsynchronizowana, jeżeli przed jej deklaracją stoi słowo kluczowe
synchronized.
Zamek gwarantuje wzajemne wykluczanie metod synchronizowanych obiektu
–
Aby wykonać metodę synchronizowaną wątek musi wejść w posiadanie zamka.
–
Wątek kończąc metodę synchronizowaną zwalnia zamek
–
Jeżeli wątek próbuje wywołać metodę synchronizowaną, a zamek jest już posiadany
przez inny wątek (wykonujący właśnie metodę synchronizowaną), to jest zostaje on
zablokowany i dodany kolejki wątków oczekujących na zwolnienie zamka.
Blokada (ang. lock) obiektu w Javie
Producent-konsument w Javie z semi-aktywnym oczekiwaniem
class ProducentKonsument {
int Licznik=0,in=0,out=0;
static final int N=100;
int Bufor[N];
public void Wstaw(int x) {
while (Licznik==N)
Thread.yield();
synchronized(this) {
Bufor[in]=x;
in=(in+1)%N;
Licznik++;
}
}
public int Pobierz() {
while(Licznik==N)
Thread.yield();
synchronized(this) {
int x=Bufor[out];
out=(out+1)%N;
Licznik=Licznik-1;
}
return x;
}
};
●
Thread.yield pozwala na przekazanie sterowania
innemu wątkowi lub procesowi.
–
●
●
●
Ciągle jest to aktywne czekanie - nie zalecane.
Synchronizowany blok kodu
–
Często nie ma konieczności synchronizowania
całej metody
–
W danej jeden wątek może wykonywać
synchronizowany blok kodu jednego obiektu.
–
Wątek ten posiada blokadę obiektu
W książce “Applied Operating Systems
Concepts” użyto synchronizowanych metod. Czy
jest to poprawne ?
Powyższe rozwiązanie jest poprawne wyłącznie
dla jednego procesu konsumenta i jednego
procesu producenta. (Dlaczego ?).
Metody wait oraz notify
●
●
Wątek posiadający blokadę obiektu może wykonać metodę wait. (tego obiektu)
–
Wątek natychmiast zwalnia blokadę (inne wątki mogą wejść w posiadanie blokady)
–
Zostaje uśpiony..
–
Umieszczany jest w kolejce wątków zawieszonych (ang. wait set) obiektu.
–
Należy obsłużyć wyjątek InterruptedException.
Wątek posiadający blokadę obiektu może wykonać metodę notify.
–
Metoda ta sprawia, że jeden wątek z kolejki wątków zawieszonych zostanie
przesunięty do kolejki wątków oczekujących na zwolnienie blokady.
●
Metoda notifyAll powoduje przeniesienie wszystkich wątków zawieszonych.
●
W Javie istnieją również metody suspend i resume.
–
Są niebezpieczne i nie należy ich stosować !!!
Blokada obiektu w Javie (wersja ostateczna)
●
Jeden wątek jest właścicielem – wykonuje kod synchronizowany
●
Entry Set - wątki oczekujące na wejście w posiadanie zamka.
●
●
●
Wait Set – wątki zawieszone poprzez metodę wait
Wywołanie metody notify przenosi wątek z wait set do entry set
Obiekt w Javie odpowiada monitorowi z maksymalnie jedną zmienną warunku. To
rozwiązanie obniża wydajność synchronizacji - każdy wątek po obudzeniu, musi
sprawdzić czy obudziło go zdarzenie na które czekał.
–
W przypadku producenta - konsumenta z ograniczonym buforem mamy dwa typy zdarzeń
(bufor niepusty oraz bufor niepełny)
Producent konsument z wykorzystaniem metod wait/notify
class ProducentKonsument {
int Licznik=0,in=0,out=0;
int Bufor[N];
public synchronized void Wstaw(int x) {
while (Licznik==N)
try { wait();}
catch(InterruptedException e) {;}
Bufor[in]=x;
in=(in+1)%N;
Licznik++;
notifyAll();
}
public synchronized int Pobierz() {
while (Licznik==0)
try { wait();}
catch(InterruptedException e) {;}
int x=Bufor[out];
out=(out+1)%N;
Licznik=Licznik-1;
notifyAll();
return x;
}
};
●
Słabość metod wait/notify - trzeba
„do skutku” sprawdzać warunek.
Synchronizacja w Javie, a monitory.
●
●
●
●
●
W monitorze możemy zadeklarować wiele zmiennych warunkowych.
Klasa w Javie w przybliżeniu odpowiada monitorowi z jedną zmienną
warunkową.
Różnice są widoczne w przypadku rozwiązania problemu producentkonsument.
–
Wersja z monitorami wykorzystuje dwie zmienne warunkowe
–
W wersji w Javie konsument oczekujący na pojawienie się elementu w buforze
może zostać powiadomiony przez innego konsumenta.
–
Z tego powodu po obudzeniu należy raz jeszcze sprawdzić warunek (pętla while).
Brak zmiennych warunkowych prowadzi do niskiej wydajności: np. budzeni są
wszyscy czekający konsumenci ale tylko jeden z nich może kontynuować.
Specyfikacja Javy mówi, że wątek wywołujący metodę notify kontynuuje
pierwszy.
–
Odpowiada to semantyce Mesa.
Biblioteka POSIX Threads - implementacja monitora
●
●
●
Dostarcza typy i operacje dla semaforów (sem_t) oraz mutexów (pthread_mutex_t)
realizujących wzajemne wykluczanie.
Dostarcza typ (pthread_cond_t) dla zmiennych warunkowych i niepodzielną operację
pthread_cond_wait(condition,mutex) usypiającą wątek na zmiennej warunkowej i
jednocześnie zwalniającą blokadę mutex. Po obudzeniu wątku oczekującego na zmiennej
warunku (przez pthread_cond_signal albo pthread_cond_broadcast) nastąpi ponowna
automatyczna re-akwizycja muteksa, przed powrotem z funkcji pthread_cond_wait.
Ponadto mamy operację na zmiennych warunkowych pthread_cond_signal (obudza jeden
zawieszony wątek) i pthread_cond_broadcast (obudza wszystkie zawieszone wątki).
pthread_mutex_t mutex; // Realizuje wzajemne wykluczanie wew. monitora
void Funkcja_Monitora() {
pthread_mutex_lock(&mutex); // Wejście do monitora
.....................
if (
) {
pthread_mutex_unlock(&mutex);
return; // Teraz też opuszczamy monitor - pamiętać o return !!!
}
......................
pthread_mutex_unlock(&mutex); // Opuszczenie monitora
}
POSIX Threads - zmienne warunkowe monitora
●
Każda zmienna warunkowa deklarowana jest jako zmienna typu pthread_condition_t i
inicjowana przy pomocy pthread_cond_init:
pthread_cond_t condition;
pthread_cond_init(&condition,NULL);
●
Jeżeli wątek przebywający wewnątrz funkcji monitora (a zatem posiadający mutex),
zechce wykonać operację wait na zmiennej warunku, może wykonać następujący kod:
// Zwolnienie muteksa i oczekiwanie na zmiennej warunku.
pthread_cond_wait(&condition,&mutex);
// Obudzenie nastąpi po wykonaniu operacji signal lub broadcast
// i re-akwizycja muteksa.
●
Wątek chcący wykonać operację signal monitora (i przebywający wewnątrz monitora
tzn. posiadający muteks) wykonuje następujący kod:
// Czy to jest semantyka Hoare'a czy też Mesa ?
pthread_cond_signal(&condition);
Przekazywanie komunikatów
(ang. message pasing)
●
Dostarcza dwie operacje.
–
send(odbiorca,dane)
–
receive(nadawca,dane)
●
Send i receive mogą wymagać podania kanału.
●
Idealne dla problemu producent konsument.
●
Na ogół wymaga kopiowania danych => możliwy spadek wydajności.
●
Dobra metoda synchronizacji dla systemów rozproszonych.
–
Wydajna realizacja pamięci współdzielonej w systemie rozproszonym jest bardzo
trudna.
Typy operacji send oraz receive
●
●
●
Blokujące send i blokujące receive.
–
Obydwa procesy są zablokowane do momentu przekazania komunikatu.
–
Nazywane spotkaniem (Ada, CSP, Occam, Parallel C)
Nieblokujące send i blokujące receive.
–
Proces wywołujący send nie musi czekać na przekazanie komunikatu.
–
Komunikat umieszczany jest w buforze
Nieblokujące send i nieblokujące receive.
–
Żaden z pary procesów nie musi czekać na przekazanie komunikatu.
–
Operacja receive sygnalizuje brak komunikatu.
–
Dodatkowa operacje test_completion i wait_completion.
Implementacje przekazywania komunikatów
●
Spotkania w Adzie
●
Gniazda
●
–
Wykorzystujące protokół TCP/IP
–
Gniazda domeny Uniksa.
Biblioteki PVM oraz MPI.
–
Zaprojektowane z myślą o obliczeniach równoległych.
●
Zdalne wywołanie procedury (ang. remote procedure call, RPC)
●
Zdalne wywołanie metody (ang. remote method invocation, RMI)
●
Kolejki komunikatów w Uniksie
●
Nazwane (i nienazwane) potoki w Uniksie

Podobne dokumenty