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