Oprogramowanie systemów równoległych i rozproszonych Wykład 2
Transkrypt
Oprogramowanie systemów równoległych i rozproszonych Wykład 2
Biblioteka Pthreads Zestaw funkcji dotyczacy ˛ watków ˛ zdefiniowany został przez norme˛ POSIX P1003.4a i nosi nazw˛e Pthreads (skrót od POSIX threads). Oprogramowanie systemów równoległych i rozproszonych 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 Dr inż. Tomasz Olas [email protected] Instytut Informatyki Teoretycznej i Stosowanej Politechnika Cz˛estochowska Wykład 2 Wykład 2 – p. 1/48 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 2 – p. 3/48 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 Tworzenie watku ˛ (I) Konwencja nazw Nowy watek tworzy sie przy pomocy funkcji 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) 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. 5/48 Tworzenie watku ˛ (II) Wykład 2 Zakończenie działania watku ˛ Funkcja wykonywana przez watek ˛ powinna mieć postać: Watek ˛ może być zakończony w nastepuj ˛ acy ˛ sposób: Nastepuje ˛ powrót z procedury startujacej ˛ (głównej procedury watku), ˛ void *f(void *arg); Przykład: Watek ˛ wywołuje funkcje pthread_exit(). void *run(void *arg) { ... } Watek ˛ jest usuwany przez inny watek ˛ za pomoca˛ funkcji pthread_cancel(), Cały proces zostaje zakończony przez wywołanie funkcji exit lub exec(). 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; } } 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. 7/48 Wykład 2 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). 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(...). 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 Wykład 2 – p. 9/48 Odłaczenie ˛ działajacego ˛ watku ˛ Zasoby watków ˛ - przykład W1 i W2 ustawiony atrybut PTHREAD_CREATE_JOINABLE Funkcja pthread_detach odłacza ˛ podany watek, ˛ co gwarantuje, że zasoby pamieci ˛ zostana˛ zwolnione natychmiast po zakończeniu działania watku. ˛ W3 i W4 ustawiony atrybut PTHREAD_CREATE_DETACHED int pthread_detach( pthread_t *thread_id); pthread_create(...) thread_id - identyfikator watku, ˛ który zostanie odłaczony. ˛ Zakonczenie pthread_exit(...) Zakonczenie i zwolnienie zasobów pthread_join(...) Zwolnienie zasobów Wykład 2 – p. 11/48 Wykład 2 Tworzenie watków ˛ - przykład (I) Tworzenie watków ˛ - przykład (II) #include <pthread.h> #include <unistd.h> #include <iostream> 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); 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; } 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. 13/48 Wykład 2 Funkcja pthread_join - przykład (I) Tworzenie watków ˛ - przykład (III) #include #include #include #include Kompilacja: g++ -o watki watki.cpp -lpthread lub <pthread.h> <iostream> <unistd.h> <fstream> g++ -pthread -o watki watki.cpp const int size = ...; Wynik działania programu: 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; 1 2 1 2 1 2 return NULL; } Wykład 2 – p. 15/48 Wykład 2 Funkcja pthread_join - przykład (II) Funkcja pthread_join - przykład (III) 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; 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 pthread_exit(NULL); } Wykład 2 – p. 17/48 Obiekty mutex Wykład 2 Tworzenie obiektu 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. 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 ma dwa stany: otwarty - nie zajety ˛ przez żaden watek, ˛ zaj˛ety - zajety ˛ przez jeden watek. ˛ mutex - wskaźnik do inicjowanego obiektu mutex, mutexattr -atrybuty tworzonego obiektu mutex (jeśli NULL ustawiane sa˛ atrybuty domyślne). 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/48 Wykład 2 Zajecie ˛ i zwolnienie obiektu mutex Funkcja pthread_mutex_lock Na obiekcie mutex wykonuje sie˛ dwie podstawowe operacje: zajecie ˛ i zwolnienie obiektu mutex. Przed dostepem ˛ do współdzielonego zasobu watek ˛ musi zapewnić sobie wyłaczność ˛ w korzystaniu z tego zasobu. Do zajecia ˛ obiektu mutex służy funkcja pthread_mutex_lock, natomiast do zwolnienia funkcja pthread_mutex_unlock. W tym celu może wywołać funkcje: Thread 1 int pthread_mutex_lock(pthread_mutex_t *mutex); mutex - zadeklarowana wcześniej i zainicjowana zmienna typu pthread_mutex_t. Thread 2 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. mutex_lock(mutex) blokada mutex_lock(mutex) uzycie zasobu odblokowanie mutex_unlock(mutex) uzycie zasobu mutex_unlock(mutex) Wykład 2 – p. 21/48 Wykład 2 Funkcja pthread_mutex_unlock Obiekty mutex - przykład Funkcja pthread_mutex_unlock zwalnia zajety ˛ mutex. // utworzenie i zainicjowanie muteksu pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_mutex_unlock(pthread_mutex_t *mutex); mutex - zadeklarowana wcześniej i zainicjowana zmienna typu pthread_mutex_t. void * run(void * arg) { ... Mutex musi być wcześniej zajety ˛ przez wołajacy ˛ watek. ˛ 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. 23/48 Wykład 2 Obiekty mutex - pozostałe funkcje Operacje na obiektach mutex - przykład (I) 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); #include <pthread.h> #include <iostream> #include <unistd.h> const int size = 100; int sum = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 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. 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; } int pthread_mutex_destroy(pthread_mutex_t * mutex); Wykład 2 – p. 25/48 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; } Wykład 2 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. tab)) Zmienna warunkowa musi być zawsze otoczona obiektem mutex, aby uniknać ˛ jednoczesnej próby oczekiwania i sygnalizowania na zmiennej warunkowej. << std::endl; tab+size/2)) << std::endl; Wykład 2 – p. 27/48 Wykład 2 Oczekiwanie na sygnał Tworzenie zmiennych warunkowych Zmienna warunkowa, to zmienna typu pthread_cond_t. Funkcja pthread_cond_wait służy do zawieszenia watku ˛ w oczekiwaniu na sygnał: Przed użyciem zmienna warunkowa musi zostać zainicjowana: Statycznie przy pomocy stałej PTHREAD_COND_INITIALIZER: pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 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 Dynamicznie przy pomocy funkcji: int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); 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. 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 Wykład 2 – p. 29/48 Sygnalizacja spełnienia warunku Zmienne warunkowe - przykład użycia Funkcja pthread_cond_signal wznawia jeden z watków ˛ oczekujacych ˛ na sygnał: Watek ˛ 1 (oczekujacy ˛ na warunek): pthread_mutex_lock(&m); pthread_cond_wait(&cond, &m); pthread_mutex_unlock(&m); 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. Watek ˛ 2 (sygnalizujacy ˛ spełnienie warunku): pthread_mutex_lock(&m); pthread_cond_signal(&cond); pthread_mutex_unlock(&m); 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: Watek 1 Watek 2 mutex_lock(m) int pthread_cond_broadcast(pthread_cond_t * cond); 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. 31/48 Wykład 2 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 2 Wykład 2 – p. 33/48 Zmienne warunkowe - przykład (I) 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 2 – p. 35/48 Wykład 2 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 2 – p. 37/48 Wykład 2 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 reakcji (o ile nie został przekazany NULL). pthread_testcancel() 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 2 – p. 39/48 Wykład 2 Jednokrotna inicjalizacja Identyfikatory watków ˛ Funkcja pthread_self zwraca unikalny identyfikator watku ˛ przydzielony mu przez system: Wywołanie funkcji pthread_once daje pewność, ze inicjalizacja bedzie ˛ wykonana tylko co najwyżej raz. pthread_t pthread_self(void); int pthread_once(pthread_once_t *once_control, void (*init_routine) (void)); Do porównania identyfikatorów dwóch watków ˛ służy funkcja: once_control - zainicjowana zmienna typu pthread_once_t, init_routine - funkcja jaka ma zostać wykonana. int pthread_equal(pthread_t thread1, pthread_t thread2); thread1 - identyfikator pierwszego watku, ˛ thread2 - identyfikator drugiego watku. ˛ Zmienna once_control musi być przed użyciem zainicjowana: pthread_once_t once_control = PTHREAD_ONCE_INIT; Funkcja zwraca niezerowa˛ wartość jeśli oba identyfikatory odnosza˛ sie˛ do tego samego watku, ˛ w przeciwnym wypadku zwraca zero. Wykład 2 Wykład 2 – p. 41/48 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 Initializacja zostala wykonana przez watek o identyfikatorze 1026 Thread - 2051 Thread - 3074 Wykład 2 – p. 43/48 Wykład 2 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. pthread_mutex_lock(&mutex); if (!init) { ... // kod wykonywany tylko przez jeden watek ˛ init = false; } else init = true; pthread_mutex_unlock(&mutex); ... } Wykład 2 Wykład 2 – p. 45/48 Bramki - implementacja (I) Bramki - implementacja (II) 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 void pthread_barrier_destroy(pthread_barrier_t* barrier) { pthread_mutex_destroy(&barrier->mutex); pthread_cond_destroy(&barrier->cond); } 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/48 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