wersja do druku - Instytut Informatyki Teoretycznej i Stosowanej
Transkrypt
wersja do druku - Instytut Informatyki Teoretycznej i Stosowanej
Plan wykładu Podstawowe pojecia ˛ i model programowania Programowanie aplikacji równoległych i rozproszonych Sposoby realizacji watków ˛ w systemach operacyjnych Tworzenie watów ˛ i zarzadzanie ˛ nimi Sposoby realizacji synchronizacji watków ˛ Metody rozwiaza ˛ ń przykładowych problemów Wykład 1 Dr inż. Tomasz Olas [email protected] Instytut Informatyki Teoretycznej i Stosowanej Politechnika Cz˛estochowska Wykład 1 – p. 1/52 Wykład 1 – p. 2/52 Wzajemne wykluczanie Procesy współbieżne Mówimy, że dwa procesy sa˛ współbieżne, jeśli jeden z nich rozpoczyna sie˛ przed zakończeniem drugiego. Obiekt, z którego może korzystać w sposób wyłaczny ˛ wiele procesów, nazywa sie˛ zasobem dzielonym. W systemach jednoprocesorowych czas pracy procesora jest dzielony pomiedzy ˛ wszystkie wykonywane współbieżnie procesy poprzez wykorzystanie zasady podziału czasu. Fragment procesu w którym korzysta on z obiektu dzielonego, nazywa sie˛ sekcja˛ krytyczna˛ tego procesu. Jeśli w systemie komputerowym jest wiele procesorów, moga˛ one wykonywać różne procesy jednocześnie. z których każdy w nieskończonej petli ˛ na przemian zajmuje sie˛ „własnymi sprawami” i wykonuje sekcje˛ krytyczna, ˛ w taki sposób, aby wykonanie jakichkolwiek dwóch lub wiecej ˛ procesów nie pokrywało sie˛ w czasie. Problem wzajemnego wykluczania: zsynchronizować N procesów, Uzależnione procesy moga˛ ze soba˛ współpracować lub współzawodniczyć. Rozwiazanie ˛ problemu wzajemnego wykluczania - dodanie instrukcji poprzedzajacych ˛ sekcje˛ krytyczna˛ (protokół wst˛epny), oraz nastepuj ˛ acych ˛ bezpośrednio po sekcji krytycznej (protokół końcowy). Praca takich procesów wymaga synchronizacji. Wykład 1 – p. 3/52 Wykład 1 – p. 4/52 Blokada i zagłodzenie Bezpieczeństwo i żywotność Blokada (zastój, zakleszczenie lub martwy punkt) - Zbiór procesów Poprawność programu sekwencyjnego: znajduje sie˛ w stanie blokady, jeśli każdy z tych procesów jest wstrzymywany w oczekiwaniu na zdarzenie, które może być spowodowane przez jakiś inny proces z tego zbioru. cz˛eściowa poprawność - jeśli sie˛ zatrzyma, to zwróci dobre wyniki, własność stopu - w ogóle sie˛ zatrzyma. Własność bezpieczeństwa - program współbieżny jest bezpieczny jeśli nigdy nie doprowadza do niepożadanego ˛ stanu (nigdy swa procesy nie znajda˛ sie˛ jednocześnie w swoich sekcjach krytycznych). Własność żywotności zapewnia, że każde pożadane ˛ zdarzenie w końcu zajdzie (jeśli jakiś proces czeka na wejście do swojej sekcji krytycznej, do w końcu do niej wejdzie). Zagłodzenie (wykluczenie) - proces nie zostaje wznowiony, mimo że zdarzenie na które czeka, wystepuje ˛ dowolna˛ ilość razy (za każdym razem gdy proces ten mógłby być wznowiony, jest wybierany jakiś inny proces). Sprawiedliwość. Wykład 1 – p. 6/52 Wykład 1 – p. 5/52 Watek ˛ - definicja Własności watków ˛ Watek ˛ (thread) można określić jako pojedyncza˛ sekwencje˛ sterowania wewnatrz ˛ procesu (podstawowa˛ jednostka˛ użycia procesora). Koszt utworzenia i przełaczenia ˛ watku ˛ jest mniejszy niż procesu. Watek ˛ wykonuje niezależny ciag ˛ instrukcji, który może być szeregowany do wykonania przez system operacyjny. Wykonanie każdego watku ˛ przebiega sekwencyjnie; każdy watek ˛ ma swój licznik rozkazów. Środowiskiem do wykonywania watku ˛ jest proces. Watki ˛ moga˛ być wykonywane na oddzielnych procesorach, co umożliwia przyspieszenie obliczeń. Dane statyczne procesu sa˛ dla watków ˛ działajacych ˛ w ramach jednego procesu wzajemnie widoczne. Tradycyjna implementacja procesu ma jeden watek ˛ sterowania. W nowszych systemach dopuszcza sie˛ istnienie wielu watków ˛ wewnatrz ˛ procesu. Wykład 1 – p. 7/52 Ponieważ watki ˛ dziela˛ wspólne dane konieczna jest synchronizacja dostepu ˛ do tych wspólnych danych. Wykład 1 – p. 8/52 Typy watków ˛ Biblioteka Pthreads Ze wzgledu ˛ na sposób implementacji rozróżnia sie˛ nastepuj ˛ ace ˛ typy watków: ˛ Zestaw funkcji dotyczacy ˛ watków ˛ zdefiniowany został przez norme˛ POSIX P1003.4a i nosi nazw˛e Pthreads (skrót od POSIX threads). Watki ˛ poziomu jadra ˛ (kernel-space threads) sa˛ implementowane Jest to zbiór typów i funkcji jezyka ˛ C. poprzez dołaczenie ˛ do każdego procesu tabeli jego watków. ˛ System zarzadza ˛ każdym watkiem ˛ wykorzystujac ˛ kwant czasu przyznany dla jego procesu rodzica (funkcja clone). Implementacja pakietu istnieje miedzy ˛ innymi w systemach Linux, QNX6, DEC OSF1. Watki ˛ poziomu użytkownika (user-space threads). Rezygnacja z Obecnie watki ˛ sa˛ cz˛eścia˛ biblioteki glibc (od wersji 2). zarzadzania ˛ watkami ˛ przez jadro. ˛ W procesie jest definiowany zbiór wykonalnych procedur, które sa˛ „wymieniane” poprzez operacje na wskaźniku stosu. Dwupoziomowy (hybrydowy) system watków ˛ (two-level threads). Połaczenie ˛ systemu watków ˛ poziomu użytkownika i jadra. ˛ Wykład 1 – p. 10/52 Wykład 1 – p. 9/52 Zasoby watku ˛ Watki ˛ POSIX - grupy funkcji Watek ˛ korzysta z zasobów procesu, ale może być szeregowany do wykonania jako niezależna jednostka w ramach procesu. Funkcje realizujace ˛ watki ˛ POSIX można podzielić na trzy grupy: Zarzadzanie ˛ watkami ˛ - funkcje do tworzenia, zarzadzania, ˛ usuwania watków, ˛ oraz funkcje zwiazane ˛ z atrybutami watków. ˛ Ma swój własny przebieg i własne zasoby lokalne: stos, Obiekty mutex - funkcje realizujace ˛ synchronizacje dostepu ˛ do rejestry, zasobów („MUTual EXclusion” - wzajemne wykluczanie). Funkcje zapewniaja˛ tworzenie, usuwanie, otwieranie i zamykanie obiektów mutex. sposób kolejkowania (szeregowania): np. priorytet, zbiór sygnałów, Zmienne warunkowe - funkcje realizujace ˛ komunikacje˛ miedzy ˛ lokalne dane watku. ˛ Pozostałe zasoby watki ˛ dziela˛ ze soba˛ w ramach procesu - pamieć, ˛ instrukcje programu, ID procesu, deskryptory plików, dzielone biblioteki, mechanizmy komunikacji miedzyprocesorowej, ˛ itd. Wykład 1 – p. 11/52 watkami ˛ dzielacymi ˛ obiekty mutex. Wykorzystuja˛ warunki zdefiniowane przez programiste. ˛ Sa˛ to funkcje do tworzenia, usuwania, czekania i wysyłania sygnału przy określonej wartości zmiennej. Wykład 1 – p. 12/52 Tworzenie watku ˛ (I) Konwencja nazw Do uruchomienia nowego watku ˛ służy funkcja pthread_create: Przedrostek funkcji Grupa funkcji pthread Funkcje watków ˛ oraz funkcje pomocnicze pthread_attr Atrybuty watków ˛ pthread_mutex Obiekty mutex pthread_mutexattr Atrybuty obiektów mutex pthread_cond Zmienne warunkowe pthread_condattr Atrybuty zmiennych warunkowych pthread_key Specyficzne dla watku ˛ klucze (dane lokalne) int pthread_create(pthread_t *thread, pthread_attr_t *attr, void* (* func)(void *), void *arg) Utworzony watek ˛ wykonuje kod funkcji fun, której adres został przekazany poprzez trzeci parametr wywołania funkcji. Do funkcji wykonywanej przez watek ˛ można przekazać dane za pomoca˛ parametru arg. Poprzez pierwszy parametr zwracany jest identyfikator watku ˛ (jest on wykorzystywany do określania watku ˛ w innych funkcjach standardu pthreads). Wykład 1 – p. 14/52 Wykład 1 – p. 13/52 Tworzenie watku ˛ (II) Zakończenie działania watku ˛ Funkcja wykonywana przez watek ˛ powinna mieć postać: Watek ˛ może być zakończony w nastepuj ˛ acy ˛ sposób: void *f(void *arg); Nastepuje ˛ powrót z funkcji wykonywanej przez watek, ˛ Przykład: Watek ˛ wywoła funkcje pthread_exit(). void *run(void *arg) { ... } Watek ˛ zostaje odwołany przez inny watek ˛ za pomoca˛ funkcji pthread_cancel(), Cały proces zostaje zakończony przez wywołanie funkcji exit() czy exec(), lub watek ˛ główny zostanie zakończony poprzez wywołanie return w funkcji main(). int main(int argc, char** argv) { ... pthread_t threadId; if (pthread_create(&threadId, NULL, run, NULL)) { std::cerr << "bład ˛ podczas tworzenia watku" ˛ << std::endl; } } Wykład 1 – p. 15/52 Wykład 1 – p. 16/52 Kończenie watku ˛ - zasoby Oczekiwanie na zakończenie watku ˛ Możliwe sa˛ dwa sposoby postepowania ˛ z zasobami zakończonych watków: ˛ Z chwila˛ zakończenia watku ˛ zwalniane sa˛ wszystkie jego zasoby. Watek ˛ może oczekiwać na zakończenie działania innego watku ˛ przez wywołanie funkcji pthread_join. int pthread_join(pthread_t thread_id, void **thread_return) thread_id - identyfikator watku ˛ na zakończenie którego bedzie ˛ czekał wołajacy ˛ watek, ˛ Zasoby zwalniane sa˛ z chwila˛ dołaczenia ˛ bieżacego ˛ watku ˛ do innego watku ˛ (który wywołał funkcje˛ pthread_join). Ustawienie sposobu postepowania ˛ z zasobami watków ˛ po ich zakończeniu jest możliwe poprzez atrybuty watku ˛ lub za pomoca˛ funkcji pthread_detach(). thread_return - jeśli jest różny od NULL, to wówczas kod zakończenia watku ˛ thid zostanie wstawiony pod adres wskazywany przez thread_return. Kodem zakończenia może być też wartość określona przy wołaniu funkcji pthread_exit lub PTHREAD_CANCELLED jeśli watek ˛ został usuniety. ˛ Watek ˛ do którego jest dołaczany ˛ dany watek ˛ musi być w stanie umożliwiajacym ˛ dołaczenie. ˛ Funkcja pthread_join powinna być wykonana dla każdego nie odłaczonego ˛ watku. ˛ Wykład 1 – p. 18/52 Wykład 1 – p. 17/52 Zasoby watków ˛ - przykład W1 i W2 ustawiony atrybut PTHREAD_CREATE_JOINABLE Tworzenie watków ˛ - przykład (I) #include <pthread.h> #include <unistd.h> #include <iostream> W3 i W4 ustawiony atrybut PTHREAD_CREATE_DETACHED void* NewThread(void* arg) { int id = *static_cast<int*>(arg); for (int i = 0; i < 3; i++) { std::cout << id << " " << std::flush; sleep(1); } return NULL; } pthread_create(...) Zakonczenie pthread_exit(...) Zakonczenie i zwolnienie zasobów pthread_join(...) Zwolnienie zasobów Wykład 1 – p. 19/52 Wykład 1 – p. 20/52 Tworzenie watków ˛ - przykład (II) Tworzenie watków ˛ - przykład (III) int main() { pthread_t thread; int id1 = 1; if (pthread_create(&thread, NULL, NewThread, (void *)(&id1))) { std::err << "bład ˛ podczas tworzenia watku ˛ nr 1" << std::endl; exit(1); } pthread_detach(thread); Kompilacja: g++ -o watki watki.cpp -lpthread lub g++ -pthread -o watki watki.cpp Wynik działania programu: 1 2 1 2 1 2 int id2 = 2; if (pthread_create(&thread, NULL, NewThread, (void *)(&id2))) { std::cerr << "bład ˛ podczas tworzenia watku ˛ nr 2" << std::endl; exit(1); } pthread_detach(thread); pthread_exit(NULL); } Wykład 1 – p. 21/52 Wykład 1 – p. 22/52 Funkcja pthread_join - przykład (I) #include #include #include #include Funkcja pthread_join - przykład (II) <pthread.h> <iostream> <unistd.h> <fstream> int main() { std::cout << "Begin Program" << std::endl; pthread_t thread; if (pthread_create(&thread, NULL, SaveThread, NULL)) { cerr << "bład ˛ podczas tworzenia watku" ˛ << std::endl; exit(1); } for (int i = 0; i < 30; i++) { sleep(1); std::cout << i << " " << flush; } void* result; pthread_join(thread, &result); std::cout << std::endl << "End program \n" << std::endl; const int size = ...; void * SaveThread(void * arg) { std::cout << "Begin Thread" << std::endl; ofstream os("ala.txt"); for (int i = 0; i < size; i++) os << "0"; std::cout << "End Thread" << std::endl; return NULL; } pthread_exit(NULL); } Wykład 1 – p. 23/52 Wykład 1 – p. 24/52 Funkcja pthread_join - przykład (III) Obiekty mutex Wynik działania programu dla size = 50000000: Begin Begin 0 1 2 21 22 Mutex jest mechanizmem wzajemnego wykluczania („MUTual EXclusion”) służacych ˛ do ochrony danych wspólnych dla watków ˛ przed jednoczesnymi modyfikacjami. Program Thread 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 23 24 25 26 27 28 29 End Thread Mechanizm ten może służyć do implementacji sekcji krytycznych, semaforów i monitorów. End program Mutex ma dwa stany: Wynik działania programu dla size = 5000000: otwarty - nie zajety ˛ przez żaden watek, ˛ zaj˛ety - zajety ˛ przez jeden watek. ˛ Begin Program Begin Thread 0 1 2 3 4 5 End Thread 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 End program Mutex nie może być jednocześnie zajety ˛ przez wiecej ˛ niż jeden watek. ˛ Watek ˛ próbujacy ˛ zajać ˛ już zajety ˛ watek ˛ zostaje wstrzymany do chwili zwolnienia mutexu przez watek, ˛ który go zajał˛ wcześniej. Wykład 1 – p. 26/52 Wykład 1 – p. 25/52 Zajecie ˛ i zwolnienie obiektu mutex Obiekty mutex - przykład Na obiekcie mutex wykonuje sie˛ dwie podstawowe operacje: zajecie ˛ i zwolnienie obiektu mutex. Do zajecia ˛ obiektu mutex służy funkcja pthread_mutex_lock, natomiast do zwolnienia funkcja pthread_mutex_unlock. Jako parametr przyjmuja˛ one wskaźnik do wcześniej utworzonego obiektu mutex. Thread 1 // utworzenie i zainicjowanie muteksu pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void * run(void * arg) { ... pthread_mutex_lock(&mutex); Thread 2 // zaj˛ ecie muteksu - protokół wst˛ epny // operacje na zasobie dzielonym - sekcja krytyczna ... mutex_lock(mutex) mutex_lock(mutex) pthread_mutex_unlock(&mutex); // zwolnienie muteksu- protokół końcowy blokada uzycie zasobu odblokowanie mutex_unlock(mutex) ... } uzycie zasobu mutex_unlock(mutex) Wykład 1 – p. 27/52 Wykład 1 – p. 28/52 Operacje na obiektach mutex - przykład (I) Operacje na obiektach mutex - przykład (II) #include <pthread.h> #include <iostream> #include <unistd.h> int main() { pthread_t thread1, thread2; int tab[size]; for (int j = 0; j < size; j++) tab[j] = j; if (pthread_create(&thread1, NULL, ForThread, { std::cerr << "bład ˛ podczas tworzenia watku" ˛ exit(1); } if (pthread_create(&thread2, NULL, ForThread, { std::cerr << "bład ˛ podczas tworzenia watku" ˛ exit(1); } pthread_join(thread1, NULL); pthread_join(thread2, NULL); std::cout << "Suma: " << sum << std::endl; return 0; } const int size = 100; int sum = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void * ForThread(void * arg) { int* tab = static_cast< int* >(arg); for (int i = 0; i < size/2; i++) { pthread_mutex_lock(&mutex); sum += tab[i]; pthread_mutex_unlock(&mutex); } return NULL; } Wykład 1 – p. 29/52 Zmienne warunkowe (I) tab)) << std::endl; tab+size/2)) << std::endl; Wykład 1 – p. 30/52 Zmienne warunkowe (II) Zmienne warunkowe sa˛ mechanizmem umożliwiajacym ˛ zawieszenie i zwolnienie czasu procesora (watku) ˛ do momentu, w którym zostanie spełniony określony warunek. Warunek ten może być dowolny i niezależny od zmiennej warunkowej, np. osiagni ˛ ecie ˛ przez zmienna˛ określonej wartości. Zmienna warunkowa musi być zawsze otoczona obiektem mutex, aby uniknać ˛ jednoczesnej próby oczekiwania i sygnalizowania na zmiennej warunkowej. Przed wykorzystaniem zmienna warunkowa musi zostać odpowiednio zainicjowana. Do oczekiwania na spełnienie warunku służy funkcja pthread_cond_wait. Funkcja pthread_cond_wait w sposób atomowy zwalnia mutex (tak jak funkcja pthread_mutex_unlock) i oczekuje na sygnał o spełnienie zmiennej warunkowej cond. Wykonanie watku ˛ jest zawieszone i nie zajmuje on czasu procesora aż do momentu odebrania sygnału od zmiennej warunkowej. Mutex musi być zajety ˛ przez watek ˛ wywołujacy ˛ pthread_cond_wait. Przed końcem działania pthread_cond_wait zajmuje mutex. Wykład 1 – p. 31/52 Wykład 1 – p. 32/52 Zmienne warunkowe - przykład użycia Watek ˛ 1 (oczekujacy ˛ na warunek): Tworzenie zmiennych warunkowych Zmienna warunkowa, to zmienna typu pthread_cond_t. pthread_mutex_lock(&m); pthread_cond_wait(&cond, &m); pthread_mutex_unlock(&m); Przed użyciem zmienna warunkowa musi zostać zainicjowana: Statycznie przy pomocy stałej PTHREAD_COND_INITIALIZER: pthread_cond_t cond = PTHREAD_COND_INITIALIZER; Watek ˛ 2 (sygnalizujacy ˛ spełnienie warunku): Dynamicznie przy pomocy funkcji: pthread_mutex_lock(&m); pthread_cond_signal(&cond); pthread_mutex_unlock(&m); Watek 1 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); cond - wskaźnik do inicjowanej zmiennej warunkowej typu pthread_cond_t, cond_attr - atrybuty zmiennej warunkowej. Gdy NULL przyjete ˛ bed ˛ a˛ Watek 2 mutex_lock(m) blokada mutex_lock(m) blokada cond_signal(c) odblokowanie ustawienie warunku cond_wait(c,m) odblokowanie atrybuty domyślne. uzycie zasobu mutex_unlock(m) odblokowanie mutex_unlock(m) Wykład 1 – p. 34/52 Wykład 1 – p. 33/52 Oczekiwanie na sygnał Sygnalizacja spełnienia warunku Funkcja pthread_cond_wait służy do zawieszenia watku ˛ w oczekiwaniu na sygnał: Funkcja pthread_cond_signal wznawia jeden z watków ˛ oczekujacych ˛ na sygnał: int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex); int pthread_cond_signal(pthread_cond_t * cond); cond - zadeklarowana i zainicjowana zmienna warunkowa. cond - zadeklarowana i zainicjowana zmienna warunkowa, mutex - wskaźnik do obiektu mutex, którym sa˛ otoczone operacje na Jeśli żaden z watków ˛ nie oczekuje na sygnał, to nic sie˛ nie dzieje. zmiennych warunkowych. Funkcja pthread_cond_wait w sposób atomowy zwalnia mutex (tak jak funkcja pthread_mutex_unlock) i oczekuje na sygnał o spełnienie zmiennej warunkowej cond. Wykonanie watku ˛ jest zawieszone i nie zajmuje on czasu procesora aż do momentu odebrania sygnału od zmiennej warunkowej. Mutex musi być zajety ˛ przez watek ˛ wołajacy ˛ pthread_cond_wait. Przed końcem działania pthread_cond_wait zajmuje mutex. Wykład 1 – p. 35/52 Jeśli w oczekiwaniu na sygnał znajduje sie˛ wiecej ˛ watków, ˛ to wznawiany jest jeden z nich. Do wznowienia wszystkich oczekujacych ˛ na sygnał watków ˛ służy funkcja: int pthread_cond_broadcast(pthread_cond_t * cond); Wykład 1 – p. 36/52 Czasowe oczekiwanie na sygnał Skasowanie zmiennej warunkowej Do czasowego zawieszenia watku ˛ w oczekiwaniu na sygnał służy funkcja: int pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex, const struct timespec * abstime); Do skasowania zmiennej warunkowej i zwolnienia jej zasobów służy funkcja: int pthread_cond_destroy( pthread_cond_t * cond); cond - zadeklarowana i zainicjowana zmienna warunkowa. Funkcja zwraca kod EBUSY jeśli nastapiła ˛ próba usuniecia ˛ zmiennej warunkowej, na której oczekiwały watki. ˛ cond - zadeklarowana i zainicjowana zmienna warunkowa, mutex - mutex, którym jest otoczona zmienna warunkowa, czas jaki watek ˛ bedzie ˛ oczekiwał na sygnał. Funkcja zwraca ETIMEDOUT jeśli sygnał nie wystapił ˛ w podanym czasie (abstime). Wykład 1 – p. 37/52 Zmienne warunkowe - przykład (I) Wykład 1 – p. 38/52 Zmienne warunkowe - przykład (II) int number; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; void* OutputThread(void* arg) { while (true) { pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); std::cout << "Wygenerowana liczba - " << number << " jest mniejsza od 5" << std::endl; pthread_mutex_unlock(&mutex); } } void* RandomThread(void* arg) { srandom(1); for (int i=0; i<20; i++) { pthread_mutex_lock(&mutex); number = static_cast<double>(rand())/RAND_MAX*10; if (number < 5) { std::cout << "mniejsza"; pthread_cond_broadcast(&cond); } pthread_mutex_unlock(&mutex); sleep(1); } } Wykład 1 – p. 39/52 Wykład 1 – p. 40/52 Odwołanie watku ˛ Zmienne warunkowe - przykład (III) int main() { pthread_t thread1; if (pthread_create(&thread1, { std::cerr << "bład ˛ podczas exit(1); } pthread_t thread2; if (pthread_create(&thread2, { std::cerr << "bład ˛ podczas exit(1); } Odwołanie jest mechanizmem, który umożliwia zakończenie działania innego watku ˛ przez dany watek. ˛ Zależnie od ustawień watek, ˛ do którego wysłano takie żadanie ˛ może je zignorować, uwzglednić ˛ to żadanie ˛ (zakończyć swoje działanie) natychmiast lub odwlec zakończenie aż do osiagni ˛ ecia ˛ pierwszego punktu zwanego punktem odwołania. NULL, RandomThread, NULL)) tworzenia watku" ˛ << std::endl; Do wysłania żadania ˛ odwołania watku ˛ służy funkcja: NULL, OutputThread, NULL)) int pthread_cancel(pthread_t thread); thread - identyfikator watku ˛ do którego wysyłane jest żadanie ˛ odwołania. tworzenia watku" ˛ << std::endl; Watek ˛ może odwołać samego siebie. void* result; pthread_join(thread1, &result); pthread_cancel(thread2); pthread_exit(NULL); } Wykład 1 – p. 42/52 Wykład 1 – p. 41/52 Punkty odwołania Sposób reakcji na żadanie ˛ odwołania Do zmiany sposobu reakcji na żadanie ˛ odwołania watku ˛ służa˛ funkcje: Punkty odwołania sa˛ miejscami w programie, gdzie wykonywany jest test, czy nie przyszło żadanie ˛ odwołania watku ˛ i w przypadku pozytywnego wyniku testu wykonywane jest odwołanie. int pthread_setcancelstate(int state , int * old_state ); Standard POSIX ustala nastepuj ˛ ace ˛ funkcje jako punkty odwołania: state - nowy typ reakcji: PTHREAD_CANCEL_ENABLE - umożliwia odwołanie, PTHREAD_CANCEL_DISBLE - powoduje ignorowanie przychodzacych ˛ pthread_join() pthread_cond_wait() żada ˛ ń odwołania, pthread_cond_timedwait() old_state - poprzez ten argument funkcja może zwrócić poprzedni typ pthread_testcancel() reakcji (o ile nie został przekazany NULL). sem_wait() int pthread_setcanceltype(int type, int * old_type); sigwait() type - nowy typ reakcji: PTHREAD_CANCEL_ASYNCHRONOUS - powodujacy ˛ natychmiastowe Funkcja pthread_testcancel() jest używana w dużych fragmentach kodu, gdy nie ma wywołania funkcji, które sa˛ punktami odwołania. zakończenie działania watku ˛ w momencie odebrania żadania ˛ odwołania. PTHREAD_CANCEL_DEFERRED - utrzymujacy ˛ działanie watku, ˛ aż do momentu osiagni ˛ ecia ˛ pierwszego z punktów odwołania. Domyślnie (PTHREAD_CANCEL_ENABLE i PTHREAD_CANCEL_DEFERRED). Wykład 1 – p. 43/52 Wykład 1 – p. 44/52 Jednokrotne wykonanie Identyfikatory watków ˛ Funkcja pthread_self zwraca unikalny identyfikator watku ˛ przydzielony mu przez system: Wywołanie funkcji pthread_once daje pewność, że dany kod zostanie wykonany tylko przez jeden (pierwszy) watek, ˛ pomimo, że funkcja ta bedzie ˛ wywoływana przez wiele watków. ˛ pthread_t pthread_self(void); Funkcja ta może zostać wykorzystana np. do inicjalizacji zmiennych wspólnych dla watków. ˛ Do porównania identyfikatorów dwóch watków ˛ służy funkcja: int pthread_equal(pthread_t thread1, pthread_t thread2); int pthread_once(pthread_once_t *once_control, void (*init_routine) (void)); thread1 - identyfikator pierwszego watku, ˛ thread2 - identyfikator drugiego watku. ˛ once_control - zainicjowana zmienna typu pthread_once_t, init_routine - funkcja jaka ma zostać wykonana. Funkcja zwraca niezerowa˛ wartość jeśli oba identyfikatory odnosza˛ sie˛ do tego samego watku, ˛ w przeciwnym wypadku zwraca zero. Zmienna once_control musi być przed użyciem zainicjowana: pthread_once_t once_control = PTHREAD_ONCE_INIT; Wykład 1 – p. 45/52 Wykład 1 – p. 46/52 Jednokrotna inicjalizacja - przykład (I) #include <pthread.h> #include <iostream.h> #include <unistd.h> Jednokrotna inicjalizacja - przykład (II) int main() { pthread_t thread[3]; for (int i = 0; i < 3; i++) { if (pthread_create(thread + i, NULL, Thread, NULL)) exit(1); } for (int i = 0; i < 3; i++) pthread_join(thread + i, NULL); pthread_once_t initControl = PTHREAD_ONCE_INIT; void Init() { std::cout << "Initializacja zostala wykonana " << "przez watek o identyfikatorze " << pthread_self() << std::endl; } return 0; } void * Thread(void * arg) { std::cout << "Thread - " << pthread_self() << std::endl; pthread_once(&initControl, Init); return NULL; } Wynik działania programu: Thread - 1026 Inicjalizacja zostala wykonana przez watek o identyfikatorze 1026 Thread - 2051 Thread - 3074 Wykład 1 – p. 47/52 Wykład 1 – p. 48/52 Jednokrotna inicjalizacja - petla ˛ Bramki bool init = true; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; Poczawszy ˛ od wersji IEEE Std 1003.1-2001 standardu wprowadzono funkcje implementujace ˛ bramki (barrier). void* run(void * arg) { ... Biblioteka glibc posiada implementacje bramek poczawszy ˛ od wersji 2.2. Synchronizacja przy użyciu bramek polega na wstrzymaniu watków ˛ aż do momentu, w którym wszystkie watki ˛ osiagn ˛ a˛ dany punkt synchronizacji (służy do tego funkcja pthread_barrier_wait). pthread_mutex_lock(&mutex); if (!init) { ... // kod wykonywany tylko przez jeden watek ˛ init = false; } else init = true; pthread_mutex_unlock(&mutex); ... } Wykład 1 – p. 49/52 Bramki - przykładowa implementacja (I) Wykład 1 – p. 50/52 Bramki - przykładowa implementacja (II) inline void pthread_barrier_destroy(pthread_barrier_t* barrier) { pthread_mutex_destroy(&barrier->mutex); pthread_cond_destroy(&barrier->cond); } Poniżej przedstawiono przykładowa˛ implementacje˛ bramek przy użyciu zmiennych warunkowych (bramek oczywiście nie trzeba implementować - sa˛ już w standardzie): struct pthread_barrier_t { int nThreads; // liczba watkow do wstrzymywania pthread_mutex_t mutex; pthread_cond_t cond; int nWaiting; // liczba aktualnie czekajacych watkow }; inline int pthread_barrier_init(pthread_barrier_t* barrier, void*, int nThreads) { barrier->nThreads = nThreads; barrier->nWaiting = nThreads - 1; pthread_mutex_init(&barrier->mutex, NULL); pthread_cond_init(&barrier->cond, NULL); return 0; } Wykład 1 – p. 51/52 inline void pthread_barrier_wait(pthread_barrier_t* barrier) { pthread_mutex_lock(&barrier->mutex); if (barrier->nWaiting) { barrier->nWaiting--; pthread_cond_wait(&barrier->cond, &barrier->mutex); } else { barrier->nWaiting = barrier->nThreads - 1; pthread_cond_broadcast(&barrier->cond); } pthread_mutex_unlock(&barrier->mutex); } Wykład 1 – p. 52/52