Oprogramowanie systemów równoległych i rozproszonych Wykład 2
Transkrypt
Oprogramowanie systemów równoległych i rozproszonych Wykład 2
Oprogramowanie systemów równoległych i rozproszonych Wykład 2 Dr inż. Tomasz Olas [email protected] Instytut Informatyki Teoretycznej i Stosowanej Politechnika Cz˛estochowska Wykład 2 – p. 1/4 Biblioteka Pthreads Zestaw funkcji dotyczacy ˛ watków ˛ zdefiniowany został przez norme˛ POSIX P1003.4a i nosi nazw˛e Pthreads (skrót od POSIX threads). Jest to zbiór typów i funkcji jezyka ˛ C. Implementacja pakietu istnieje miedzy ˛ innymi w systemach Linux, QNX6, DEC OSF1. Obecnie watki ˛ sa˛ cz˛eścia˛ biblioteki glibc (od wersji 2). Wykład 2 – p. 2/4 Zasoby watku ˛ Watek ˛ korzysta z zasobów procesu, ale może być szeregowany do wykonania jako niezależna jednostka w ramach procesu. Ma swój własny przebieg i własne zasoby lokalne: stos, rejestry, sposób kolejkowania (szeregowania): np. priorytet, zbiór sygnałów, 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 2 – p. 3/4 Watki ˛ POSIX - grupy funkcji 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. ˛ Obiekty mutex - funkcje realizujace ˛ synchronizacje dostepu ˛ do zasobów („MUTual EXclusion” - wzajemne wykluczanie). Funkcje zapewniaja˛ tworzenie, usuwanie, otwieranie i zamykanie obiektów mutex. Zmienne warunkowe - funkcje realizujace ˛ komunikacje˛ miedzy ˛ 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 2 – p. 4/4 Konwencja nazw 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) Wykład 2 – p. 5/4 Tworzenie watku ˛ (I) Nowy watek tworzy sie przy pomocy funkcji pthread_create. Funkcja ta tworzy watek, którego kod znajduje sie w funkcji podanej jako argument func. Watek jest uruchamiany z parametrem arg, a informacja o nim jest umieszczana w strukturze thread. int pthread_create(pthread_t *thread, pthread_attr_t *attr, void (* func)(void *), void *arg) thread - identyfikator watku ˛ (wartość nadawana przez funkcje), attr - atrybuty watku, ˛ gdy NULL przyjete ˛ domyślne, func - procedura zawierajaca ˛ kod watku, ˛ arg - argument przekazywany do watku, ˛ funkcja zwraca: 0 - sukces, -1 - bład. ˛ Wykład 2 – p. 6/4 Tworzenie watku ˛ (II) Funkcja wykonywana przez watek ˛ powinna mieć postać: void *f(void *arg); Przykład: void *run(void *arg) { ... } 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 2 – p. 7/4 Zakończenie działania watku ˛ Watek ˛ może być zakończony w nastepuj ˛ acy ˛ sposób: Nastepuje ˛ powrót z procedury startujacej ˛ (głównej procedury watku), ˛ Watek ˛ wywołuje funkcje pthread_exit(). Watek ˛ jest usuwany przez inny watek ˛ za pomoca˛ funkcji pthread_cancel(), Cały proces zostaje zakończony przez wywołanie funkcji exit lub exec(). Jawne zakończenie działania bieżacego ˛ watku ˛ nastepuje ˛ poprzez wywołanie funkcji: int pthread_exit(void ** retval) retval - wartość zwracana przez watek ˛ (przekazywana do watków ˛ czekajacych ˛ pthread_join). Wykład 2 – p. 8/4 Kończenie watku ˛ - zasoby Możliwe sa˛ dwa sposoby postepowania ˛ z zasobami zakończonych watków: ˛ Z chwila˛ zakończenia watku ˛ zwalniane sa˛ wszystkie jego zasoby. Zasoby zwalniane sa˛ z chwila˛ dołaczenia ˛ bieżacego ˛ watku ˛ do innego watku ˛ (który wywołał funkcje˛ pthread_join). Sposób postepowania ˛ z watkiem ˛ jest uzależniony od atrybutu ustawionego w momencie tworzenia watku. ˛ PTHREAD_CREATE_DETACHED - zasoby watku ˛ zwalniane sa˛ tuż po jego zakończeniu, PTHREAD_CREATE_JOINABLE - zasoby watku ˛ zwalniane sa˛ po wywołaniu funkcji pthread_join(...). Wykład 2 – p. 9/4 Oczekiwanie na zakończenie watku ˛ 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, ˛ 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 2 – p. 10/4 Zasoby watków ˛ - przykład W1 i W2 ustawiony atrybut PTHREAD_CREATE_JOINABLE W3 i W4 ustawiony atrybut PTHREAD_CREATE_DETACHED pthread_create(...) Zakonczenie pthread_exit(...) Zakonczenie i zwolnienie zasobów pthread_join(...) Zwolnienie zasobów Wykład 2 – p. 11/4 Odłaczenie ˛ działajacego ˛ watku ˛ Funkcja pthread_detach odłacza ˛ podany watek, ˛ co gwarantuje, że zasoby pamieci ˛ zostana˛ zwolnione natychmiast po zakończeniu działania watku. ˛ int pthread_detach( pthread_t *thread_id); thread_id - identyfikator watku, ˛ który zostanie odłaczony. ˛ Wykład 2 – p. 12/4 Tworzenie watków ˛ - przykład (I) #include <pthread.h> #include <unistd.h> #include <iostream> 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; } Wykład 2 – p. 13/4 Tworzenie watków ˛ - przykład (II) 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); 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 2 – p. 14/4 Tworzenie watków ˛ - przykład (III) Kompilacja: g++ -o watki watki.cpp -lpthread lub g++ -pthread -o watki watki.cpp Wynik działania programu: 1 2 1 2 1 2 Wykład 2 – p. 15/4 Funkcja pthread_join - przykład (I) #include #include #include #include <pthread.h> <iostream> <unistd.h> <fstream> 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; } Wykład 2 – p. 16/4 Funkcja pthread_join - przykład (II) 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; pthread_exit(NULL); } Wykład 2 – p. 17/4 Funkcja pthread_join - przykład (III) Wynik działania programu dla size = 50000000: Begin Begin 0 1 2 21 22 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 End program Wynik działania programu dla size = 5000000: 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 Wykład 2 – p. 18/4 Obiekty mutex Mutex jest mechanizmem wzajemnego wykluczania („MUTual EXclusion”) służacych ˛ do ochrony danych wspólnych dla watków ˛ przed jednoczesnymi modyfikacjami. Mechanizm ten może służyć do implementacji sekcji krytycznych, semaforów i monitorów. Mutex ma dwa stany: otwarty - nie zajety ˛ przez żaden watek, ˛ zaj˛ety - zajety ˛ przez jeden watek. ˛ 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 2 – p. 19/4 Tworzenie obiektu mutex Obiekt mutex jest zmienna˛ typu pthread_mutex_t. Może zostać zainicjowany poprzez: Stała˛ PTHREAD_MUTEX_INITIALIZER: pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER; Funkcje˛ pthread_mutex_init: int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * mutexattr); mutex - wskaźnik do inicjowanego obiektu mutex, mutexattr -atrybuty tworzonego obiektu mutex (jeśli NULL ustawiane sa˛ atrybuty domyślne). Wykład 2 – p. 20/4 Zajecie ˛ i zwolnienie obiektu mutex 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. Thread 1 Thread 2 mutex_lock(mutex) mutex_lock(mutex) blokada uzycie zasobu odblokowanie mutex_unlock(mutex) uzycie zasobu mutex_unlock(mutex) Wykład 2 – p. 21/4 Funkcja pthread_mutex_lock Przed dostepem ˛ do współdzielonego zasobu watek ˛ musi zapewnić sobie wyłaczność ˛ w korzystaniu z tego zasobu. W tym celu może wywołać funkcje: int pthread_mutex_lock(pthread_mutex_t *mutex); mutex - zadeklarowana wcześniej i zainicjowana zmienna typu pthread_mutex_t. Jeśli mutex jest wolny, to zostaje zajety ˛ oraz przypisany watkowi ˛ wołajacemu. ˛ Funkcja natychmiast kończy działanie. Jeśli mutex jest zajety ˛ przez inny watek, ˛ to zawiesza działanie watku ˛ do momentu, kiedy mutex zostanie zwolniony. Wykład 2 – p. 22/4 Funkcja pthread_mutex_unlock Funkcja pthread_mutex_unlock zwalnia zajety ˛ mutex. int pthread_mutex_unlock(pthread_mutex_t *mutex); mutex - zadeklarowana wcześniej i zainicjowana zmienna typu pthread_mutex_t. Mutex musi być wcześniej zajety ˛ przez wołajacy ˛ watek. ˛ Wykład 2 – p. 23/4 Obiekty mutex - przykład // utworzenie i zainicjowanie muteksu pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void * run(void * arg) { ... pthread_mutex_lock(&mutex); // zaj˛ ecie muteksu - protokół wst˛ epny // operacje na zasobie dzielonym - sekcja krytyczna ... pthread_mutex_unlock(&mutex); // zwolnienie muteksu- protokół końcowy ... } Wykład 2 – p. 24/4 Obiekty mutex - pozostałe funkcje Funkcja pthread_mutex_trylock zachowuje sie˛ podobnie jak ˛ funkcja pthread_mutex_lock. Nie blokuje ona jednak wołajacego watku, ˛ ale w przypadku zajetego ˛ obiektu mutex zwraca EBUSY. int pthread_mutex_trylock(pthread_mutex_t *mutex); Do kasowania obiektu mutex służy funkcja pthread_mutex_destroy. Niszczy obiekt mutex oraz zwalnia zwiazane ˛ z nim zasoby. Mutex musi być wolny, w przeciwnym wypadku zwracana jest wartość EBUSY. int pthread_mutex_destroy(pthread_mutex_t * mutex); Wykład 2 – p. 25/4 Operacje na obiektach mutex - przykład (I) #include <pthread.h> #include <iostream> #include <unistd.h> 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 2 – p. 26/4 Operacje na obiektach mutex - przykład (II) 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; } tab)) << std::endl; tab+size/2)) << std::endl; Wykład 2 – p. 27/4 Zmienne warunkowe 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. Wykład 2 – p. 28/4 Tworzenie zmiennych warunkowych Zmienna warunkowa, to zmienna typu pthread_cond_t. Przed użyciem zmienna warunkowa musi zostać zainicjowana: Statycznie przy pomocy stałej PTHREAD_COND_INITIALIZER: pthread_cond_t cond = PTHREAD_COND_INITIALIZER; Dynamicznie przy pomocy funkcji: 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˛ atrybuty domyślne. Wykład 2 – p. 29/4 Oczekiwanie na sygnał Funkcja pthread_cond_wait służy do zawieszenia watku ˛ w oczekiwaniu na sygnał: int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex); cond - zadeklarowana i zainicjowana zmienna warunkowa, mutex - wskaźnik do obiektu mutex, którym sa˛ otoczone operacje na zmiennych warunkowych. Funkcja pthread_cond_wait w sposób atomowy zwalnia mutex (tak jak funkcja pthread_mutex_unlock) i oczekuje na sygnał o ˛ jest spełnienie zmiennej warunkowej cond. Wykonanie watku 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 2 – p. 30/4 Sygnalizacja spełnienia warunku Funkcja pthread_cond_signal wznawia jeden z watków ˛ oczekujacych ˛ na sygnał: int pthread_cond_signal(pthread_cond_t * cond); cond - zadeklarowana i zainicjowana zmienna warunkowa. Jeśli żaden z watków ˛ nie oczekuje na sygnał, to nic sie˛ nie dzieje. 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 2 – p. 31/4 Zmienne warunkowe - przykład użycia Watek ˛ 1 (oczekujacy ˛ na warunek): pthread_mutex_lock(&m); pthread_cond_wait(&cond, &m); pthread_mutex_unlock(&m); Watek ˛ 2 (sygnalizujacy ˛ spełnienie warunku): pthread_mutex_lock(&m); pthread_cond_signal(&cond); pthread_mutex_unlock(&m); Watek 1 Watek 2 mutex_lock(m) blokada mutex_lock(m) blokada cond_signal(c) odblokowanie ustawienie warunku cond_wait(c,m) odblokowanie uzycie zasobu mutex_unlock(m) odblokowanie mutex_unlock(m) Wykład 2 – p. 32/4 Czasowe oczekiwanie na sygnał 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); 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 2 – p. 33/4 Skasowanie zmiennej warunkowej 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. ˛ Wykład 2 – p. 34/4 Zmienne warunkowe - przykład (I) int number; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 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 2 – p. 35/4 Zmienne warunkowe - przykład (II) 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); } } Wykład 2 – p. 36/4 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); } NULL, RandomThread, NULL)) tworzenia watku" ˛ << std::endl; NULL, OutputThread, NULL)) tworzenia watku" ˛ << std::endl; void* result; pthread_join(thread1, &result); pthread_cancel(thread2); pthread_exit(NULL); } Wykład 2 – p. 37/4 Odwołanie watku ˛ 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. Do wysłania żadania ˛ odwołania watku ˛ służy funkcja: int pthread_cancel(pthread_t thread); thread - identyfikator watku ˛ do którego wysyłane jest żadanie ˛ odwołania. Watek ˛ może odwołać samego siebie. Wykład 2 – p. 38/4 Sposób reakcji na żadanie ˛ odwołania Do zmiany sposobu reakcji na żadanie ˛ odwołania watku ˛ służa˛ funkcje: int pthread_setcancelstate(int state , int * old_state ); state - nowy typ reakcji: PTHREAD_CANCEL_ENABLE - umożliwia odwołanie, PTHREAD_CANCEL_DISBLE - powoduje ignorowanie przychodzacych ˛ żada ˛ ń odwołania, old_state - poprzez ten argument funkcja może zwrócić poprzedni typ reakcji (o ile nie został przekazany NULL). int pthread_setcanceltype(int type, int * old_type); type - nowy typ reakcji: PTHREAD_CANCEL_ASYNCHRONOUS - powodujacy ˛ natychmiastowe 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 2 – p. 39/4 Punkty odwołania 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. Standard POSIX ustala nastepuj ˛ ace ˛ funkcje jako punkty odwołania: pthread_join() pthread_cond_wait() pthread_cond_timedwait() pthread_testcancel() sem_wait() sigwait() Funkcja pthread_testcancel() jest używana w dużych fragmentach kodu, gdy nie ma wywołania funkcji, które sa˛ punktami odwołania. Wykład 2 – p. 40/4 Identyfikatory watków ˛ Funkcja pthread_self zwraca unikalny identyfikator watku ˛ przydzielony mu przez system: pthread_t pthread_self(void); Do porównania identyfikatorów dwóch watków ˛ służy funkcja: int pthread_equal(pthread_t thread1, pthread_t thread2); thread1 - identyfikator pierwszego watku, ˛ thread2 - identyfikator drugiego watku. ˛ Funkcja zwraca niezerowa˛ wartość jeśli oba identyfikatory odnosza˛ sie˛ do tego samego watku, ˛ w przeciwnym wypadku zwraca zero. Wykład 2 – p. 41/4 Jednokrotna inicjalizacja Wywołanie funkcji pthread_once daje pewność, ze inicjalizacja bedzie ˛ wykonana tylko co najwyżej raz. int pthread_once(pthread_once_t *once_control, void (*init_routine) (void)); once_control - zainicjowana zmienna typu pthread_once_t, init_routine - funkcja jaka ma zostać wykonana. Zmienna once_control musi być przed użyciem zainicjowana: pthread_once_t once_control = PTHREAD_ONCE_INIT; Wykład 2 – p. 42/4 Jednokrotna inicjalizacja - przykład (I) #include <pthread.h> #include <iostream.h> #include <unistd.h> pthread_once_t initControl = PTHREAD_ONCE_INIT; void Init() { std::cout << "Initializacja zostala wykonana " << "przez watek o identyfikatorze " << pthread_self() << std::endl; } void * Thread(void * arg) { std::cout << "Thread - " << pthread_self() << std::endl; pthread_once(&initControl, Init); return NULL; } Wykład 2 – p. 43/4 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); return 0; } Wynik działania programu: Thread - 1026 Initializacja zostala wykonana przez watek o identyfikatorze 1026 Thread - 2051 Thread - 3074 Wykład 2 – p. 44/4 Jednokrotna inicjalizacja - petla ˛ bool init = true; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; void* run(void * arg) { ... pthread_mutex_lock(&mutex); if (!init) { ... // kod wykonywany tylko przez jeden watek ˛ init = false; } else init = true; pthread_mutex_unlock(&mutex); ... } Wykład 2 – p. 45/4 Bramki Poczawszy ˛ od wersji IEEE Std 1003.1-2001 standardu wprowadzono funkcje implementujace ˛ bramki (barrier). Biblioteka glibc posiada implementacje bramek poczawszy ˛ od wersji 2.2. Wykład 2 – p. 46/4 Bramki - implementacja (I) 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 2 – p. 47/4 Bramki - implementacja (II) inline void pthread_barrier_destroy(pthread_barrier_t* barrier) { pthread_mutex_destroy(&barrier->mutex); pthread_cond_destroy(&barrier->cond); } 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 2 – p. 48/4