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