Zapoznaj się z dokumentacją następujących funkcji biblioteki Pthread:
pthread_create
: tworzy nowy wątek i inicjuje go wywołaniem podanej
funkcji,pthread_join
: oczekuje na zakończenie wykonania wątku, na którym
została wywołana ta funkcja,pthread_exit
: kończy działanie bieżącego wątku i zwraca wartość;
zwróć uwagę na specjalne działanie w przypadku wywołania w wątku głównym
programu,pthread_mutex_init
: inicjuje obiekt muteksu, który służy do
synchronizacji dostępu do zasobów współdzielonych,pthread_mutex_lock
i pthread_mutex_unlock
: blokują i
odblokowują mutex, aby zapewnić wyłączny dostęp do chronionego obszaru
kodu.Napisz w ANSI C prosty program wykorzystujący Pthreads, który:
"Hello OpSys. Written by
thread ID"
(gdzie ID jest identyfikatorem wątku), a następnie wywołuje
pthread_exit
aby zakończyć wątek.Uwaga 1: kompilacja programów z wykorzystaniem biblioteki Pthread wymaga
podania opcji -l
z nazwą biblioteki w wywołaniu kompilatora:
cc -o myhello myhello.c -lpthread
(a) Uruchom swój program myhello
kilka razy i zwróć uwagę na jego
wyjście, i w szczególności kolejność uruchamiania i wykonywania wątków.
Czy jest tym, czego oczekiwałeś? Sforułuj wnioski. Dla porównania, możesz
skompilować i uruchomić dołączony przykładowy program hello.c
.
(b) Dodaj wątkom w stworzonym programie wykonywanie konkretnej pracy obliczeniowej:
for (i=1; i<1000; ++i) wynik += log(i);
Użyj flagi -lm
kompilacji w celu dolinkowania biblioteki matematycznej.
Ponownie uruchom program wiele razy i porównaj zachowanie wątków.
(c) Następnie przejrzyj, skompiluj i uruchom dostarczony program bug5.c
.
Wyjaśnij co obserwujesz i jak to naprawić.
Jak to się porównuje z sytuacją, gdyby zamiast wątków w programie były wykorzystane podprocesy?
(d) Zmniejsz liczbę iteracji w wątkach do 10. Uruchom program co najmniej 5 razy, obserwując kolejność zakończenia wątków. Następnie dodaj do programu barierę, aby wymusić jednoczesne wystartowanie obliczeń wszystkich wątków. Ponownie uruchom program co najmniej 5 razy, podsumuj uzyskany wynik, i porównaj z poprzednim.
hello_arg1.c
oraz hello_arg2.c
.
Zauważ w jaki sposób do wątku przekazywany jest pojedynczy argument,
oraz jak przekazać wiele argumentów przez strukturę.
Skompiluj oraz uruchom oba programy i obserwuj wyjście.bug3.c
. Co poszło nie tak?
Jak to naprawić?(a) Napisz w języku ANSI C program obliczający sumę kolejnych N
liczb
pierwszych poczynając od M
.
Program powinien sprawdzać kolejno wszystkie liczby od M
wzwyż, czy nie
dzielą się przez jakąkolwiek liczbę większą od 1 i mniejszą od niej samej.
W przypadku gdy liczba dzieli się przez cokolwiek w tym przedziale, jej
sprawdzanie powinno być natychmiast porzucane (bo nie jest pierwsza), a w
przeciwnym wypadku liczba powinna być dodawana do obliczanej sumy, i
licznik znalezionych liczb pierwszych powinien być inkrementowany.
Procedura zatrzymuje się gdy ten licznik osiągnie zadaną wartość N
, która
powinna być odczytywana jako pierwszy argument wywołania programu, i suma
powinna być wyświetlona na wyjściu.
Dla potwierdzenia poprawności działania programu na przykład sprawdź, że
suma pierwszych 10000 liczb pierwszych (N=10000;M=2
) wynosi 496165411.
Potwierdź to w raporcie.
(b) Aby przyspieszyć działanie programu wykorzystując wielordzeniowy procesor komputera, zamień program na wielowątkowy. Wykorzystaj następujące zmienne globalne:
int N; /* liczba liczb pierwszych do znalezienia (arg.wywolania) */
int M; /* poczatkowa liczba do sprawdzania (drugi arg.wywolania, domyslnie 2) */
int K; /* liczba watkow obliczeniowych (trzeci arg.wywolania, domyslnie 1) */
int sprawdz = M; /* nastepna liczba do sprawdzania */
int znalezione = 0; /* liczba znalezionych i zsumowanych l.pierwszych */
long suma = 0; /* to bedzie obliczona suma liczb pierwszych */
Program powinien utworzyć K
wątków obliczeniowych, z których każdy
będzie powtarzalnie wykonywał następujące kroki:
1. pobierz aktualną wartość liczby `sprawdz` i natychmiast zinkrementuj ją
2. sprawdź czy liczba `sprawdz` jest pierwsza
3. jeśli tak, to dodaj ją do `suma` i zinkrementuj `znalezione`
4. jeśli wartość `znalezione` jest równa lub większa `N` to zakończ pracę
5. w przeciwnym wypadku powtórz kroki od 1.
Liczba wątków obliczeniowych K
powinna być zadana trzecim argumentem
wywołania programu. Wątek główny (funkcja main
) powinien czekać na
zakończenie pracy wszystkich wątków obliczeniowych, po czym wyświetlać na
wyjściu obliczoną sumę liczb pierwszych.
Uruchom program wielokrotnie dla różnych wartości N
i K
(M=2
) i
porównaj otrzymywane wyniki z wersją jednowątkową. Odpowiedz w raporcie na
pytania:
(c) Dodaj w programie następujące dwa muteksy:
pthread_mutex_t muteks_pobierania;
pthread_mutex_t muteks_sumowania;
Dodaj kod poprawnie inicjalizujący oba muteksy, a następnie dodaj w wątkach obliczeniowych ochronę kroku pobierania i inkrementowania liczby do sprawdzenia pierwszym muteksem, oraz ochronę kroku sumowania liczb pierwszych i inkrementowania ich liczby drugim muteksem. Zadbaj by sekcje krytyczne chronione muteksami miały minimalny zakres.
Ponownie uruchom wielokrotnie program dla różnych wartości N
i K
(M=2
) i porównaj otrzymywane wyniki z wersją jednowątkową. Odpowiedz w
raporcie na pytania:
time
. K
liczbę fizycznych rdzeni
systemu komputerowego, minus 1 lub 2.K
razy szybciej
niż jednowątkowa, to spróbuj wyjaśnić dlaczego.N
i K
.N=250000;M=2
) oraz stu kolejnych liczb pierwszych poczynając od liczby
1000000 (N=100;M=1000000
), i podaj w raporcie otrzymane wartości oraz
użytą liczbę K
i czasy działania programu.