Obliczenia równoległe w zagadnieniach in˙zynierskich Wykład 1
Transkrypt
Obliczenia równoległe w zagadnieniach in˙zynierskich Wykład 1
Obliczenia równoległe w zagadnieniach inżynierskich Wykład 1 Dr inż. Tomasz Olas [email protected] Instytut Informatyki Teoretycznej i Stosowanej Politechnika Cz˛estochowska Wykład 1 – p. 1/43 Plan wykładu Podstawowe pojecia ˛ i model programowania 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 – p. 2/43 Procesy współbieżne Mówimy, że dwa procesy sa˛ współbieżne, jeśli jeden z nich rozpoczyna sie˛ przed zakończeniem drugiego. W systemach jednoprocesorowych czas pracy procesora jest dzielony pomiedzy ˛ wszystkie wykonywane współbieżnie procesy poprzez wykorzystanie zasady podziału czasu. Jeśli w systemie komputerowym jest wiele procesorów, moga˛ one wykonywać różne procesy jednocześnie. Uzależnione procesy moga˛ ze soba˛ współpracować lub współzawodniczyć. Praca takich procesów wymaga synchronizacji. Wykład 1 – p. 3/43 Wzajemne wykluczanie Obiekt, z którego może korzystać w sposób wyłaczny ˛ wiele procesów, nazywa sie˛ zasobem dzielonym. Fragment procesu w którym korzysta on z obiektu dzielonego, nazywa sie˛ sekcja˛ krytyczna˛ tego procesu. Problem wzajemnego wykluczania: zsynchronizować N procesów, 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. Rozwiazanie ˛ problemu wzajemnego wykluczania - dodanie instrukcji poprzedzajacych ˛ sekcje˛ krytyczna˛ (protokół wst˛epny), oraz nastepuj ˛ acych ˛ bezpośrednio po sekcji krytycznej (protokół końcowy). Wykład 1 – p. 4/43 Bezpieczeństwo i żywotność Poprawność programu sekwencyjnego: 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). Sprawiedliwość. Wykład 1 – p. 5/43 Blokada i zagłodzenie Blokada (zastój, zakleszczenie lub martwy punkt) - Zbiór procesów 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. 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). Wykład 1 – p. 6/43 Watek ˛ - definicja Watek ˛ (thread) można określić jako pojedyncza˛ sekwencje˛ sterowania wewnatrz ˛ procesu (podstawowa˛ jednostka˛ użycia procesora). Watek ˛ wykonuje niezależny ciag ˛ instrukcji, który może być szeregowany do wykonania przez system operacyjny. Środowiskiem do wykonywania watku ˛ jest proces. Tradycyjna implementacja procesu ma jeden watek ˛ sterowania. W nowszych systemach dopuszcza sie˛ istnienie wielu watków ˛ wewnatrz ˛ procesu. Wykład 1 – p. 7/43 Własności watków ˛ Koszt utworzenia i przełaczenia ˛ watku ˛ jest mniejszy niż procesu. Dane statyczne procesu sa˛ dla watków ˛ działajacych ˛ w ramach jednego procesu wzajemnie widoczne. Wykonanie każdego watku ˛ przebiega sekwencyjnie; każdy watek ˛ ma swój licznik rozkazów. Watki ˛ moga˛ być wykonywane na oddzielnych procesorach, co umożliwia przyspieszenie obliczeń. Ponieważ watki ˛ dziela˛ wspólne dane konieczna jest synchronizacja dostepu ˛ do tych wspólnych danych. Wykład 1 – p. 8/43 Typy watków ˛ Ze wzgledu ˛ na sposób implementacji rozróżnia sie˛ nastepuj ˛ ace ˛ typy watków: ˛ Watki ˛ poziomu jadra ˛ (kernel-space threads) sa˛ implementowane 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). Watki ˛ poziomu użytkownika (user-space threads). Rezygnacja z 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. 9/43 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 1 – p. 10/43 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 1 – p. 11/43 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 1 – p. 12/43 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 1 – p. 13/43 Tworzenie watku ˛ (I) Do uruchomienia nowego watku ˛ służy funkcja pthread_create: 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/43 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 1 – p. 15/43 Zakończenie działania watku ˛ Watek ˛ może być zakończony w nastepuj ˛ acy ˛ sposób: Nastepuje ˛ powrót z funkcji wykonywanej przez watek, ˛ Watek ˛ wywoła funkcje pthread_exit(). 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(). Wykład 1 – p. 16/43 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). Ustawienie sposobu postepowania ˛ z zasobami watków ˛ po ich zakończeniu jest możliwe poprzez atrybuty watku ˛ lub za pomoca˛ funkcji pthread_detach(). Wykład 1 – p. 17/43 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 1 – p. 18/43 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 1 – p. 19/43 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 1 – p. 20/43 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 1 – p. 21/43 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 1 – p. 22/43 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 1 – p. 23/43 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 1 – p. 24/43 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 1 – p. 25/43 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 1 – p. 26/43 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. Jako parametr przyjmuja˛ one wskaźnik do wcześniej utworzonego obiektu mutex. Thread 1 Thread 2 mutex_lock(mutex) mutex_lock(mutex) blokada uzycie zasobu odblokowanie mutex_unlock(mutex) uzycie zasobu mutex_unlock(mutex) Wykład 1 – p. 27/43 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 1 – p. 28/43 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 1 – p. 29/43 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 1 – p. 30/43 Zmienne warunkowe (I) 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. Wykład 1 – p. 31/43 Oczekiwanie na spełnienie warunku 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. 32/43 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 1 – p. 33/43 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 1 – p. 34/43 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 1 – p. 35/43 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 1 – p. 36/43 Jednokrotne wykonanie 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. ˛ Funkcja ta może zostać wykorzystana np. do inicjalizacji zmiennych wspólnych dla watków. ˛ 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 1 – p. 37/43 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 1 – p. 38/43 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 1 – p. 39/43 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 1 – p. 40/43 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. 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). Wykład 1 – p. 41/43 Bramki - przykładowa implementacja (I) 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. 42/43 Bramki - przykładowa 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 1 – p. 43/43