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