Wątki to wydzielone sekwencje przewidzianych do wykonania instrukcji, które są
realizowane w ramach jednego procesu. Każdy wątek ma swój własny stos, zestaw rejestrów, licznik
programowy, indywidualne dane, zmienne lokalne, i informację o stanie. Wszystkie wątki danego
procesu mają jednak tę samą przestrzeń adresową., ogólną obsługę sygnałów, pamięć wirtualną,
dane oraz wejście-wyjście. W ramach procesu wielowątkowego każdy wątek wykonuje się
oddzielnie i asynchronicznie.
Wszystkie programy korzystające z funkcji operujących na wątkach normy POSIX zawierają
dyrektywę #include <pthread.h>. Kompilując przy użyciu gcc programy korzystające z tej
biblioteki, należy wymusić jej dołączenie, przez użycie opcji -lpthread:
gcc program.c -lpthread
Każdy proces zawiera przynajmniej jeden główny wątek początkowy, tworzony przez system
operacyjny w momencie stworzenia procesu. By do procesu dodać nowy wątek należy wywołać
funkcję pthread_create. Nowo utworzony wątek zaczyna się od wykonania funkcji użytkownika
(przekazanej mu przez argument pthread_create). Wątek działa aż do czasu wystąpienia jednego z
następujących zdarzeń: zakończenia funkcji, wywołania funkcji pthread_exit, anulowania wątku za
pomocą funkcji pthread_cancel, zakończenia procesu macierzystego wątku, wywołania funkcji
exec przez jeden z wątków.
Przetwarzanie realizowane przez wątki musi być odpowiednio synchronizowane, tak, by wykonując
operacje na wspólnych strukturach danych nie dopuścić do niespójności danych. Stosowane są dwie
metody zapewnienia odpowiedniej koordynacji wątków: korzystanie z zamków (czyli blokad
wzajemnie wykluczających, dalej nazywanych muteksami od nazw funkcji) lub korzystanie z
konstrukcji nazywanych zmiennymi warunkowymi.
Zmienną muteksową można porównać do semafora binarnego, który wątki mogą posiadać. Mutex
albo zezwala na dostęp, albo go zabrania. Zamknięcia muteksu może dokonać dowolny wątek
znajdujący się w jego zasięgu, natomiast otworzyć go może tylko wątek który go zamknął. Wątki,
które nie mogą uzyskać dostępu do muteksu są blokowane w oczekiwaniu na niego. Operacje
wykonywane na muteksach są niepodzielne. Jeśli za pomocą muteksów trzeba synchronizować
wątki kilku procesów, należy odwzorować muteks w obszar pamięci współdzielonej dostępny dla
wszystkich procesów.
UWAGA: Przed uruchomieniem każdego z poniższego kodu postaraj się przewidzieć jego rezultat. Nastepnie skompiluj i uruchom program.
Program 1
Poniższy program składa się z dwóch wątków – program główny także jest traktowany jako wątek
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void *Watek(void *arg){
int i;
for ( i=0; i<5; i++ ) {
printf("Nowy wątek mówi cześć!\n");
sleep(1);
}
return NULL;
}
int main(void) {
pthread_t nowy_watek; // zmienna zawierająca identyfikator wątku
if ( pthread_create( &nowy_watek, NULL, Watek, NULL) ) { // tworzenie nowego wątku, realizującego instrukcje z funkcji Watek()
printf("błąd przy tworzeniu wątku\n");
abort();
}
if ( pthread_join ( nowy_watek, NULL ) ){ // łączenie nowego wątku z wątkiem głównym
printf("błąd w kończeniu wątku\n");
exit(0);
}
printf("Stary wątek mówi cześć!\n"); // główny wątek wypisuje komunikat
exit(0);
}
Program 2
Program, podobnie jak poprzedni, tworzy nowy wątek. Zarówno ten wątek, jak i
wątek główny inkrementują zmienną "zmiennaglobalna" dwudziestokrotnie. Jednakże w
wyniku współbieżnego działania wątki nadpisują zapisywane przez siebie wartości. Dodatkowo
znaki "o" i "." pokazują, który wątek akurat wypisuje swój komunikat. Przetestuj kilka razy.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int zmiennaglobalna=0;
void *Watek(void *arg) {
int i,j;
for ( i=0; i<20; i++ ) {
j=zmiennaglobalna;
j=j+1;
printf(".");
fflush(stdout);
sleep(1);
zmiennaglobalna=j;
}
return NULL;
}
int main(void) {
pthread_t nowy_watek;
int i;
if ( pthread_create( &nowy_watek, NULL, Watek, NULL) ) {
printf("błąd przy tworzeniu wątku.");
abort();
}
for ( i=0; i<20; i++) {
zmiennaglobalna=zmiennaglobalna+1;
printf("o");
fflush(stdout);
sleep(1);
}
if ( pthread_join ( nowy_watek, NULL ) ) {
printf("błąd przy kończeniu wątku.");
abort();
}
printf("\nZmienna globalna wynosi %d\n", zmiennaglobalna);
exit(0);
}
Program 3
W kolejnym programie pojawiają się wywołania funkcji
pthread_mutex_lock oraz pthread_mutex_unlock. Umożliwiają one wykonywanie jednoczesnych
operacji. Gdy jeden wątek ma odblokowany muteks, to drugi ma zablokowany. Zatem jeśli wątek A
próbuje zablokować muteks, podczas gdy wątek B już go blokuje, to wówczas A zostaje uśpiony. Jak
tylko B zwolni muteks (dzięki funkcji pthread_mutex_unlock()), A będzie w stanie go zablokować
na swój użytek (innymi słowy, pthread_mutex_lock() zwróci kod sukcesu). Analogicznie, jeśli wątek
C spróbuje zablokować ten muteks, podczas gdy A już blokuje, to C zostanie uśpiony na jakiś czas.
Wszystkie wątki, które zostaną uśpione, będą kolejkowane do danego muteksu.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int zmiennaglobalna=0;
pthread_mutex_t muteks=PTHREAD_MUTEX_INITIALIZER;
void *Watek(void *arg) {
int i,j;
for ( i=0; i<20; i++ ) {
pthread_mutex_lock(&muteks);
j=zmiennaglobalna;
j=j+1;
printf(".");
fflush(stdout);
sleep(1);
zmiennaglobalna=j;
pthread_mutex_unlock(&muteks);
}
return NULL;
}
int main(void) {
pthread_t nowy_watek;
int i;
if ( pthread_create( &nowy_watek, NULL, Watek, NULL) ) {
printf("błąd przy tworzeniu wątku.");
abort();
}
for ( i=0; i<20; i++) {
pthread_mutex_lock(&muteks);
zmiennaglobalna=zmiennaglobalna+1;
pthread_mutex_unlock(&muteks);
printf("o");
fflush(stdout);
sleep(1);
}
if ( pthread_join ( nowy_watek, NULL ) ) {
printf("błąd przy kończeniu wątku.");
abort();
}
printf("\nZmienna globalna wynosi %d\n", zmiennaglobalna);
exit(0);
}
Zadanie
Proszę napisać program współbieżny z dwoma lub czterema wątkami, który pomnoży macierz przez liczbę ( https://sites.google.com/site/obliczeniowo/ma/macierze/mnozenie-macierzy-przez-liczb). Połowa (jedna czwarta)
wierszy będzie mnożona przez jeden wątek a druga (następna ) przez kolejny. Porównać czas wykonania programu współbieżnego z programem
jednowątkowym ( bez wątków). Można skorzystać np. z biblioteki time.h.
#include <time.h>
#include <stdio.h>
int main () {
time_t begin, end;
time(&begin);
// tutaj wpisujemy instrukcje
time(&end);
time_t elapsed = end - begin;
printf("Czas: %ld sekund\n", elapsed);
Przetestować dla różnych rozmiarów macierzy, np 1000 na 1000, 10000 na 10000.
Zadanie nieobowiązkowe
Proszę napisać program współbieżny, realizujący technikę "dziel i rządź", np. sortowanie szybkie.