--> niektóre aplikacje "z natury rzeczy" powinny składać się z
wielu
procesów (np serwery usług)
takie rozwiązanie ma tę wadę, że jest kosztowne
z punktu widzenia systemu operacyjnego
gdyż tworzenie nowych procesów wymaga wielu zabiegów
(ze strony s.o.),
dlatego właśnie wymyślono wątki - ich
tworzenie
jest mniej kosztowne, a także
komunikacja między wątkami jest łatwiejsza niż
między
procesami ...
--> do tworzenia programów wielowątkowych na Linuxie używamy
biblioteki "pthread";
zawiera ona procedury obsługi wątków zgodne z normą
POSIX;
nazwy wszystkich jej funkcji zaczynają się od
przedrostka
"pthread_";
dokumentacja każdej funkcji jest dostępna w manualu
oraz w "pthr3big.txt";
(zauważmy że wątki są bardzo podobne do "procesów"
języka BACI ...)
--> przypomnijmy najważniejsze cechy wątków:
--> funkcje biblioteki pthread zwracają wartość 0, jeśli
zakończyły
się sukcesem,
zwracają wartość >0 jeśli zakończyły się błędem
(to jest właśnie kod błędu);
NIE modyfikują zmiennej "errno" !!!
Przykłady w plikach o nazwach "watek??.cc" kompiluje się następująco:
g++ watek01.cc -lpthread -o watek01(jeśli używamy funkcji matematycznych, np sin, to należy dołączyć dodatkowo bibliotekę matematyczną "m")
g++ watek01.cc -lpthread -lm -o watek01
--> "watek01.cc"
jest to podstawowy przyklad w ktorym sa tworzone
watki;
przy pomocy funkcji pthread_create()
watki te zwracaja swoj "id" (otrzymywany przez pthread_self());
glowny watek (wykonujacy funkcje main()) po
utworzeniu
watkow probuje je wcielić przy pomocy pthread_join();
(jest to operacja analogiczna do wait() w przypadku
procesow)
pobierajac przy okazji ich "status zakonczenia"
- w tym wypadku ich "id";
innymi słowy: przy pomocy
pthread_join()
można czekać na zakończenie
innego wątku, różnica w porównaniu z procesami
polega
na tym że
dowolny wątek może czekać na zakończenie dowolnego
innego wątku
(a nie tylko macierzysty na zakonczenie potomnego
jak w przypadku
procesów !)
dopiero po wcieleniu watek oddaje zasoby
(chyba ze jest to watek "odlaczony" = "detached"
do odlaczania sluzy funkcja pthread_detach)
watki wykonuja kod funkcji podanej przez 3
parametr
funkcji pthread_create();
funkcja ta otrzymuje jako 1 argument 4 parametr
funkcji pthread_create()
(można w ten sposób przekazać jakąś informacje do
wątku)
szczegolna role pelni typ (void*) - sluzy jako
uniwersalny
typ danych - np do przekazywania statusu zakonczenia
--> "watek02.cc"
(kompilować TYLKO pod Digital Unix-em !)
w tym przykladzie kazdy watek zawiera kod
wykonujacy
pętle z bardzo wieloma obrotami
watki uzywaja "polityki przelaczania kontekstu"
SCHED_FIFO
(ktora podobno jest uzalezniona od priorytetow)
jeden z watkow ma nizszy priorytet niz pozostale
oznacza to, ze powinien pozostac nieco w tyle
w porownaniu z innymi watkami (jest to wątek z
"i=3")
mozemy sie przekonac ze rzeczywiscie tak jest
uruchamiajac ten program i przeadresowujac do pliku
watek02 > watek02.txtaby zmienic "polityke przelaczania" i "priorytety"
--> "watek03.cc"
w tym przykladzie mamy zastosowanie MUTEX-ow
mutex <=> mutual exclusion <=> wzajemne wykluczanie
mutex-y są podobne do semaforów binarnych ...
------------- własności muteksów -------------
istnieja mutexy "wewnątrzprocesowe" i
"miedzyprocesowe"
(miedzyprocesowe musza byc umieszczane we wspolnej
pamieci)
deklaracja mutexu (wewnątrzprocesowego):
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;do zamykania i otwierania muteksow slużą następujące funkcje:
pthread_mutex_lock(&m); // zamkniecie;w przykladzie "watek03.cc" istnieja dwie zmienne globalne
// jesli nie jest mozliwe to funkcja blokuje
pthread_mutex_unlock(&m); // otwarcie
pthread_mutex_trylock(&m); // zamkniecie bez blokowania
// jesli niemozliwe to zwraca EBUSY
konto1-=kwota; konto2+=kwota;gdzie kwota jest losowo wybraną liczbą; oczywiscie na koncu suma konto1+konto2
--> zmienne warunkowe CONDITION
Przy pomocy zmiennych condition, muteksów
oraz klasy C++ można skonstruować coś
co przypomina monitor
...
W przykładzie "watek04.cc" mamy klasę/monitor, posiadającą jedną procedurę o nazwie "Procedura" (wykorzystuje się tutaj muteks, aby zapewnić że tylko jeden wątek może przebywać w monitorze); zwróć uwagę na sposób inicjowania muteksu w klasie (jedyny możliwy !); wszystkie wątki próbują wywoływać tę procedurę w pętli, każde wywołanie procedury zwiększa wartość zmiennej liczCalosciowy, jest także zapamiętywane ile razy dany wątek "przebywał" w monitorze. Wypróbuj ten przykład; sprawdź co się stanie po "wzięciu w komentarz" operacji zamykania/otwierania muteksu w procedurze Procedura.
Podobnie jak to było w monitorach, zmienne
condition pozwalają zorganizować
"kolejkę uśpionych procesów, oczekujących
na jakieś zdarzenie" ...
Naszkicujemy teraz ogólnie, jak rozwiązać
problem producenta/konsumenta
(nieskończony bufor) ...
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// muteks, który "ochrania" dostep do bufora
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// deklaracja zmiennej "condition"
//W przykładzie "watek05.cc" mamy rozwiązanie problemu producenta/konsumenta z nieskończonym buforem za pomocą monitora (czyli klasy C++ z muteksami i zmiennymi conditon) o nazwie "Bufor". Przetestuj ten przykład.
// ------------ konsument ------------
//
while(1)
{
pthread_mutex_lock(&mutex);
// zamykamy muteks
while( 'bufor jest pusty' )
pthread_cond_wait(&cond, &mutex);
/* powoduje ze mutex zostanie otworzony a "konsument" uśpiony
i umieszczony w kolejce uśpionych procesów zmiennej "cond"
gdy "producent" obudzi "konsumenta" wtedy
muteks zostanie automatycznie zamknięty a konsument
będzie kontynuował działanie ...
[pętla "while(warunek)" jest podobno niezbędna
nie wystarczy "if(warunek)" ...]
*/
//
// teraz czytamy z bufora ...
//
pthread_mutex_unlock(&mutex);
// otwieramy muteks
//
// konsument robi coś innego ...
//
}
//
// ------------ producent ------------
//
while(1)
{
pthread_mutex_lock(&mutex);
//
// piszemy do bufora ...
//
pthread_cond_signal(&cond);
// budzimy "konsumenta"
pthread_mutex_unlock(&mutex);
// "konsument" zacznie działać dopiero, gdy otworzymy mutex (???)
//
// producent robi coś innego ...
//
}
Zadanie 73 (*)
Zadanie 74
Porównaj wyniki działania programu na różnych systemach operacyjnych.
Dodatkowe wskazówki:
wątek 1 tworzy napisy przy pomocy sprintf() i wysyła go używając
długiego bufora (np 50-bajtowego);
wątki 2,3,4 czytają dane także używając długiego bufora;
chodzi o zaobserwowanie że w niektórych os komunikaty wracają pocięte
przypadkowo, w innych przychodzą w postaci niezmienionej ...