POSIX Threads/Specjalne działania
Stos funkcji finalizujących (cleanup)
edytujZ każdym wątkiem związany jest stos funkcji finalizujących (cleanup stack) - jest to osobny stos na którym zapisywane są funkcje użytkownika i argumenty dla nich (przez pthread_cleanup_push), które następnie mogą być wywołane wprost przy ściąganiu ze stosu (pthread_cleanup_pop).
Ponadto stos funkcji jest automatycznie czyszczony - tzn. ściągane i wykonywane są kolejne funkcje przy asynchronicznym przerwaniu wątku oraz gdy wątek jest kończony wywołaniem pthread_exit. Jeśli wątek kończy się wykonaniem instrukcji return, wówczas to programista jest odpowiedzialny za wyczyszczenie stosu poprzez wywołanie odpowiednią liczbę razy funkcji pthread_cleanup_pop. Co prawda standard nakłada na implementację konieczność zagwarantowania, że te dwie funkcje występują w bloku kodu (makra preprocesora), jednak wyjście z bloku instrukcją return, break, continue lub goto jest niezdefiniowane. (Np. w Cygwinie otrzymałem błąd SIGSEGV, w Linuxie błąd został po cichu zignorowany).
Funkcja użytkownika zwraca i przyjmuje argument typu void*.
Zastosowaniem opisanego mechanizmu jest zwalnianie zasobów przydzielonych wątkowi - np. zwolnienie blokad, dealokacja pamięci, zamknięcie plików, gniazd. Jest on nieco podobny do znanego z języka C++ automatycznego wołania destruktorów przy opuszczaniu zakresu, w którym zostały utworzone.
Funkcje
edytuj- int pthread_cleanup_push(void (fun*)(void*), void *arg) (doc)
- odłożenie na stos adresu funkcji fun, która przyjmuje argument arg
- int pthread_cleanup_pop(int execute) (doc)
- zdjęcie ze stosu funkcji i jeśli execute jest różne od zera, wykonanie jej
Przykład
edytujW przykładowym programie dwa wątki alokują pamięć. Jeden wątek wprost wywołuje funkcję pthread_cleanup_pop, w drugim funkcja finalizująca jest wywoływana automatycznie po wykonaniu pthread_exit.
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h> // sleep
#define test_errno(info) do {if (errno) {perror(info); exit(EXIT_FAILURE);}} while(0)
/* funkcja finalizująca */
void zwolnij_pamiec(void* adres) {
printf("zwalnianie pamięci spod adresu %p\n", adres);
free(adres);
}
//------------------------------------------------------------------------
void* watek(void* id) {
char* tablica1 = malloc(100);
char* tablica2 = NULL;
printf("wątek #%d zaalokował 100 bajtów pod adresem %p\n", (int)id, tablica1);
pthread_cleanup_push(zwolnij_pamiec, tablica1);
if (tablica1) {
tablica2 = malloc(200);
printf("wątek #%d zaalokował 200 bajtów pod adresem %p\n", (int)id, tablica2);
pthread_cleanup_push(zwolnij_pamiec, tablica2);
if ((int)id > 0)
/* wątek się kończy w tym punkcie, funkcje finalzujące
zostaną uruchomione */
pthread_exit(NULL);
pthread_cleanup_pop(1);
}
pthread_cleanup_pop(1);
printf("wątek #%d zakończył się\n", (int)id);
return NULL;
}
//------------------------------------------------------------------------
int main() {
pthread_t id1;
pthread_t id2;
/* utworzenie 2 wątków */
errno = pthread_create(&id1, NULL, watek, (void*)(0));
test_errno("pthread_create (1)");
errno = pthread_create(&id2, NULL, watek, (void*)(1));
test_errno("pthread_create (2)");
/* oczekiwanie na zakończenie */
pthread_join(id1, NULL);
pthread_join(id2, NULL);
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------
Wyjście:
$ ./przyklad wątek #0 zaalokował 100 bajtów pod adresem 0x9058098 wątek #1 zaalokował 100 bajtów pod adresem 0x9058190 wątek #0 zaalokował 200 bajtów pod adresem 0x90581f8 wątek #1 zaalokował 200 bajtów pod adresem 0x90582c8 zwalnianie pamięci spod adresu 0x90581f8 zwalnianie pamięci spod adresu 0x9058098 wątek #0 zakończył się zwalnianie pamięci spod adresu 0x90582c8 zwalnianie pamięci spod adresu 0x9058190
Lokalne dane wątku
edytujW pthreads istnieje możliwość przyporządkowania kluczom, które są jednakowe dla wszystkich wątków, wskaźnika do danych specyficznych dla danego wątku. W istocie jest to powiązanie pary (klucz, wątek) z danymi, przy czym odwołanie do danych wymaga podania jedynie klucza - wywołujący wątek jest domyślnym drugim elementem pary. Ułatwia to m.in. przekazywanie danych do funkcji wywoływanych z poziomu wątków.
Z kluczem można związać funkcję (destruktor), która jest wywoływana przy zakończeniu wątku jeśli dane wątku są różne od NULL. Gdy istnieje więcej kluczy, kolejność wywoływania destruktorów jest nieokreślona.
Jeśli klucz został utworzony (pthread_key_create) to dane dla nowotworzonego wątku są automatycznie inicjowane na wartość NULL. Błędem jest próba sięgnięcia lub zapisania danych dla nieistniejącego klucza.
Liczba dostępnych kluczy jest ograniczona stałą PTHREAD_KEY_MAX.
Typ
edytuj- pthread_key_t
Funkcje
edytuj- int pthread_key_create(pthread_key_t *key, void (destructor*)(void*)) (doc)
- utworzenie nowego klucza, przypisanie destruktora
- int pthread_key_delete(pthread_key_t key) (doc)
- usunięcie klucza
- int pthread_setspecific(pthread_key_t key, const void *data) (doc)
- przypisanie do klucza danych wątku
- void* pthread_getspecific(pthread_key_t key) (doc)
- pobranie danych związanych z kluczem.
Przykład
edytujW programie tworzony jest jeden klucz, z którym wątki kojarzą napis - przedrostek, którym funkcja wyswietl poprzedza wyświetlane komunikaty.
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#define test_errno(info) do {if (errno) {perror(info); exit(EXIT_FAILURE);}} while(0)
pthread_key_t klucz;
/* funkcja wypsuje wiersz, poprzedząjąc go prefiksem przypisanym do wątku */
void wyswietl(const char* napis) {
char* prefiks = (char*)pthread_getspecific(klucz);
if (prefiks == NULL)
/* należy zabezpieczyć się przed sytuacją, gdy wywołujący
wątek nie przyporządkował nic do klucza */
puts(napis);
else
printf("%s: %s\n", prefiks, napis);
}
//---------------------------------------------------------------------------
/* destruktor klucza */
void destruktor(void* napis) {
printf("wywołano destruktor, adres pamięci do zwoleniania: %p ('%s')\n",
napis,
(char*)napis
);
free(napis);
}
//---------------------------------------------------------------------------
void* watek(void* napis) {
/* ustawienie prefiksu w lokalnych danych wątku */
int status = pthread_setspecific(klucz, napis);
if (status)
fprintf(stderr, "pthread_setspecific: %s\n", strerror(status));
else
printf("adres napisu: %p ('%s')\n", napis, (char*)napis);
wyswietl("Witaj w równoległym świecie!");
sleep(1);
wyswietl("Wątek wykonuje pracę");
sleep(1);
wyswietl("Wątek zakończony");
return NULL;
}
//---------------------------------------------------------------------------
char* strdup(const char*);
#define N 3
int main() {
pthread_t id[N];
int i;
char* prefiks[3] = {"***", "!!!", "###"}; // prefiksy dla komunikatów z wątków
/* utworzenie klucza */
errno = pthread_key_create(&klucz, destruktor);
test_errno("pthread_key_create");
/* utworzenie wątków */
for (i=0; i < N; i++) {
errno = pthread_create(&id[i], NULL, watek, (void*)strdup(prefiks[i % 3]));
test_errno("pthread_create");
}
/* oczekiwanie na ich zakończenie */
for (i=0; i < N; i++)
pthread_join(id[i], NULL);
/* usunięcie klucza */
errno = pthread_key_delete(klucz);
test_errno("pthread_key_delete");
return EXIT_SUCCESS;
}
//---------------------------------------------------------------------------
char* strdup(const char* s) {
char *d = NULL;
if (s) {
d = (char*)malloc(strlen(s)+1);
if (d)
strcpy(d, s);
}
return d;
}
//---------------------------------------------------------------------------
Przykładowe wyjście:
adres napisu: 0x9bd6008 ('***') adres napisu: 0x9bd60a8 ('!!!') !!!: Witaj w równoległym świecie! adres napisu: 0x9bd6148 ('###') ###: Witaj w równoległym świecie! ***: Witaj w równoległym świecie! !!!: Wątek wykonuje pracę ***: Wątek wykonuje pracę ###: Wątek wykonuje pracę !!!: Wątek zakończony wywołano destruktor, adres pamięci do zwolnienia: 0x9bd60a8 ('!!!') ***: Wątek zakończony wywołano destruktor, adres pamięci do zwolnienia: 0x9bd6008 ('***') ###: Wątek zakończony wywołano destruktor, adres pamięci do zwolnienia: 0x9bd6148 ('###')
Funkcje wywoływane jednokrotnie
edytujCzasem istnieje potrzeba jednokrotnego wykonania jakiejś funkcji, np. w celu inicjalizacji jakiś globalnych ustawień, biblioteki, otwarcia plików, gniazd itp. Pthreads udostępnia funkcję pthread_once, która niezależnie od liczby wywołań, uruchamia dokładnie raz funkcję użytkownika.
Jednokrotne uruchomienie gwarantuje obiekt typu pthread_once_t; zmienną tego typu należy statycznie zainicjować wartością PTHREAD_ONCE_INIT.
Typy
edytuj- pthread_once_t
Funkcje
edytuj- int pthread_once(pthread_once_t *once, void (fun*)(void)) (doc)
Przykład
edytuj#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)
/* obiekt gwarantujący jednokrotne wykonanie, musi zostać zainicjowany */
pthread_once_t program_gotowy = PTHREAD_ONCE_INIT;
void inicjalizacja() {
/* inicjalizacja, np. prekalkulowanie jakiś tablic,
otwieranie pliku logowania itp. */
puts("Rozpoczynanie programu");
}
//------------------------------------------------------------------------
void* watek(void* numer) {
pthread_once(&program_gotowy, inicjalizacja);
printf("Uruchomiono wątek nr %d\n", (int)numer);
return NULL;
}
//------------------------------------------------------------------------
#define N 10 /* liczba wątków */
int main() {
pthread_t id[N];
int i;
/* utworzenie wątków */
for (i=0; i < N; i++) {
errno = pthread_create(&id[i], NULL, watek, (void*)i);
test_errno("pthread_create");
}
/* oczekiwanie na jego zakończenie */
for (i=0; i < N; i++) {
errno = pthread_join(id[i], NULL);
test_errno("pthread_join");
}
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------
Wynik:
$ ./przyklad Rozpoczynanie programu Uruchomiono wątek nr 0 Uruchomiono wątek nr 2 Uruchomiono wątek nr 1 Uruchomiono wątek nr 3 Uruchomiono wątek nr 4 Uruchomiono wątek nr 5 Uruchomiono wątek nr 6 Uruchomiono wątek nr 7 Uruchomiono wątek nr 8 Uruchomiono wątek nr 9
UNIX-owe sygnały
edytujGdy sygnał zostanie dostarczony do procesu nie jest określone w kontekście którego wątku wykona się procedura obsługi sygnału.
Blokowanie sygnałów
edytujPthreads umożliwia zablokowanie określonych sygnałów na poziomie wątków, służy temu funkcja pthread_sigmask (analogiczna do sigprocmask), która modyfikuje zbiór zablokowanych sygnałów wywołującego ją wątku. Nie można zablokować SIGFPE, SIGILL, SIGSEGV ani SIGBUS
Funkcje
edytuj- int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset) (doc)
- how określa jak zbiór set wpływa na bieżący zbiór
- SIG_BLOCK - włączenie wskazanych sygnałów do zbioru
- SIG_UNBLOCK - odblokowanie wskazanych sygnałów
- SIG_SETMASK - zastąpienie bieżącego zbioru nowym
- gdy oset nie jest pusty, poprzednia maska sygnałów zapisywana jest pod wskazywanym adresem
- how określa jak zbiór set wpływa na bieżący zbiór
Wysyłanie sygnałów do wątków
edytujWysyłanie sygnału do określonego wątku umożliwia funkcja pthread_kill. Jeśli numer sygnału jest równy zero, wówczas nie jest wysyłany sygnał, ale są testowane jedynie ewentualne błędy - a więc czy wskazany identyfikator wątku jest poprawny.
Uwaga!
|
Funkcje
edytuj- int pthread_kill(pthread_t id, int signum) (doc)
- id - wątek
- signum - numer sygnału
Przykład
edytujW przykładowym programie wątek główny czeka na sygnał SIGUSR1, który po pewnym czasie wysyła utworzony wcześniej wątek.
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h> // sleep
#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)
pthread_t main_id; // id głównego wątek
// funkcja wątku
void* watek(void* nieuzywany) {
puts("\twątek się rozpoczął");
sleep(1);
puts("\twątek wysyła sygnał SIGUSR1 do głównego wątku");
errno = pthread_kill(main_id, SIGUSR1);
test_errno("pthread_kill");
return NULL;
}
//------------------------------------------------------------------------
int main() {
pthread_t id;
int signum;
sigset_t mask;
// blokowanie SIGUSR1
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
errno = pthread_sigmask(SIG_BLOCK, &mask, NULL);
test_errno("pthread_kill");
// odczyt id głównego wątku
main_id = pthread_self();
// utworzenie wątku
errno = pthread_create(&id, NULL, watek, NULL);
test_errno("pthread_create");
// oczekiwanie na sygnał
puts("wątek główny oczekuje na sygnał");
sigwait(&mask, &signum);
test_errno("sigwait");
if (signum == SIGUSR1)
puts("wątek główny otrzymał sygnał SIGUSR1");
else
printf("wątek główny otrzymał sygnał %d\n", signum);
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------
Wyjście:
$ ./przyklad wątek główny oczekuje na sygnał wątek się rozpoczął wątek wysyła sygnał SIGUSR1 do głównego wątku wątek główny otrzymał sygnał SIGUSR1
Przerywanie wątków
edytujWskazany wątek może zostać przerwany, jeśli tylko nie zostało to wprost zabronione. Sygnał przerwania wysyła funkcja pthread_cancel.
Sposób przerwania wątku jest dwojaki:
- Asynchroniczny - przerwanie następuje natychmiast, w dowolnym momencie.
- Opóźniony (deferred) - przerwanie następuje dopiero po osiągnięciu tzw. punktu przerwania (cancellation point), tj. wywołania określonych funkcji systemowych (np. sleep, read). Standard POSIX określa, które funkcje muszą, a które mogą być punktami przerwania, definiuje także dodatkowo pthread_testcancel(void). Pełna lista funkcji znajduje się w rozdziale 2.9.5 Thread Cancellation);
Ustawienie flagi kontrolującej możliwość przerwania wątku wykonuje funkcja pthread_setcancelstate, akceptuje dwie wartości:
- PTHREAD_CANCEL_ENABLE - przerwanie możliwe;
- PTHREAD_CANCEL_DISABLE - przerwanie niemożliwe; jeśli jednak wystąpi żądanie przerwania, fakt ten jest pamiętany i gdy stan flagi zmieni się na PTHREAD_CANCEL_ENABLE wątek zostanie przerwany.
Wybór sposobu przerywania umożliwia funkcja pthread_setcanceltype, która akceptuje dwie wartości:
- PTHREAD_CANCEL_ASYNCHRONOUS - przerwanie asynchroniczne,
- PTHREAD_CANCEL_DEFERRED - przerwanie opóźnione.
Obie funkcje zmieniają parametry wywołującego je wątku.
Funkcje
edytuj- int pthread_cancel(pthread_t thread) (doc)
- przerwanie wskazanego wątku
- int pthread_setcancelstate(int state, int *oldstate) (doc)
- ustawia możliwość przerwania i zwraca poprzednią wartość
- int pthread_setcanceltype(int type, int *oldtype) (doc)
- ustawia sposób przerwania i zwraca poprzednią wartość
- void pthread_testcancel(void) (doc)
- punkt przerwania
Przykład
edytujPrzykładowy program uruchamia trzy wątki z różnymi ustawieniami dotyczącymi przerywania:
- dopuszcza przerwanie asynchroniczne,
- dopuszcza przerwanie opóźnione,
- przez pewien czas w ogóle blokuje przerwania.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h> // pause w watek3
#include <errno.h>
#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)
void zakonczenie(void* numer) {
printf("funkcja finalizująca dla wątku #%d\n", (int)numer);
}
//------------------------------------------------------------------------
void* watek1(void* numer) {
int i, n;
pthread_cleanup_push(zakonczenie, numer);
errno = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
test_errno("pthread_setcancelstate");
errno = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
test_errno("pthread_setcanceltype");
printf("\turuchomiono wątek #%d (przerwanie asynchroniczne)\n", (int)numer);
while (1) {
n = 1000000;
for (i=0; i < n; i++)
/**/;
}
pthread_cleanup_pop(1);
return NULL;
}
//------------------------------------------------------------------------
void* watek2(void* numer) {
int i, n;
pthread_cleanup_push(zakonczenie, numer);
errno = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
test_errno("pthread_setcancelstate");
errno = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
test_errno("pthread_setcanceltype");
printf("\turuchomiono wątek #%d (przerwanie opóźnione)\n", (int)numer);
while (1) {
pthread_testcancel(); // punkt przerwania
n = 1000000;
for (i=0; i < n; i++)
/**/;
}
pthread_cleanup_pop(1);
return NULL;
}
//------------------------------------------------------------------------
void* watek3(void* numer) {
pthread_cleanup_push(zakonczenie, numer);
errno = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
test_errno("pthread_setcancelstate");
printf("\turuchomiono wątek #%d (przez 2 sekundy nie można przerwać)\n", (int)numer);
sleep(2);
printf("\twątek #%d można już przerwać\n", (int)numer);
errno = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
test_errno("pthread_setcancelstate");
pause();
pthread_cleanup_pop(1);
return NULL;
}
//------------------------------------------------------------------------
void przerwanie(pthread_t id, const char* napis) {
printf("%s: wysyłanie sygnału przerwania do wątku\n", napis);
errno = pthread_cancel(id);
test_errno("pthread_cancel");
printf("%s: wysłano, oczekiwanie na zakończenie\n", napis);
errno = pthread_join(id, NULL);
test_errno("pthread_join");
printf("%s: wątek zakończony\n", napis);
}
//------------------------------------------------------------------------
int main() {
pthread_t id[3];
/* utworzenie wątków */
errno = pthread_create(&id[0], NULL, watek1, (void*)(0));
test_errno("pthread_create (1)");
errno = pthread_create(&id[1], NULL, watek2, (void*)(1));
test_errno("pthread_create (2)");
errno = pthread_create(&id[2], NULL, watek3, (void*)(2));
test_errno("pthread_create (3)");
/* przerywanie kolejnych wątków */
przerwanie(id[0], "#0");
przerwanie(id[1], "#1");
przerwanie(id[2], "#2");
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------
Przykładowe wyjście:
$ ./przyklad uruchomiono wątek #0 (przerwanie asynchroniczne) #0: wysyłanie sygnału przerwania do wątku #0: wysłano, oczekiwanie na zakończenie uruchomiono wątek #2 (przez 2 sekundy nie można przerwać) funkcja finalizująca dla wątku #0 #0: wątek zakończony #1: wysyłanie sygnału przerwania do wątku #1: wysłano, oczekiwanie na zakończenie uruchomiono wątek #1 (przerwanie opóźnione) funkcja finalizująca dla wątku #1 #1: wątek zakończony #2: wysyłanie sygnału przerwania do wątku #2: wysłano, oczekiwanie na zakończenie wątek #2 można już przerwać funkcja finalizująca dla wątku #2 #2: wątek zakończony
Pthreads i forkowanie
edytujBiblioteka zarządza trzema listami funkcji, które są wykonywane przy forkowaniu procesu. Jedna lista przechowuje adresy funkcji wywoływanych przed właściwym uruchomieniem funkcji fork, dwie kolejne zawierają funkcje wykonywane tuż po zakończeniu fork, osobno w procesie rodzica i potomnym.
Funkcja pthread_atfork służy do dodawania do list funkcji użytkownika; można podawać puste wskaźniki.
Funkcje wykonywane po forku są wykonywane w kolejności zgodnej z dodawaniem ich do list, natomiast przed forkiem są uruchamiane w kolejności odwrotnej.
Zastosowaniem tego mechanizmu może być ponowna inicjalizacja wątków w procesie potomnym. Przede wszystkim przy forkowaniu w procesie potomnym działa tylko jeden wątek - główny wątek, wykonujący funkcję main. Ponadto konieczna jest ponowna inicjalizacja różnych obiektów synchronizujących (np. mutexów), bowiem - jak wspomniano we wstępie - samo skopiowanie pamięci jest niewystarczającego do utworzenia w pełni funkcjonalnej kopii takiego obiektu.
Funkcje
edytuj- int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)) (doc)
- prepare - funkcja wykonywana przed fork()
- parent - funkcja wykonywana po fork() w procesie rodzica
- child - funkcja wykonywana po fork() w procesie potomnym
Przykład
edytujW programie proces potomny odtwarza jeden działający wątek.
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h> // sleep
#include <sys/wait.h> // waitpid
#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)
void* watek(void* numer) {
printf("\turuchomiono wątek #%d\n", (int)numer);
while (1) {
printf("\t\twątek #%d w procesie #%d\n", (int)numer, getpid());
usleep(700*1000);
}
return NULL;
}
//------------------------------------------------------------------------
#define N 3 /* liczba wątków */
pthread_t id[N];
void inicjalizacja_watkow() {
int i;
printf("tworzenie %d wątków w procesie %d\n", N, getpid());
/* utworzenie wątków */
for (i=0; i < N; i++) {
errno = pthread_create(&id[i], NULL, watek, (void*)(i+1));
test_errno("pthread_create");
}
}
//------------------------------------------------------------------------
int main() {
pid_t pid;
puts("początek programu");
inicjalizacja_watkow();
/* rejestrowanie funkcji wykonywanej w procesie potomnym */
errno = pthread_atfork(NULL, NULL, inicjalizacja_watkow);
test_errno("pthread_atfork");
sleep(1);
pid = fork();
printf("fork => %d\n", pid);
switch (pid) {
case -1:
test_errno("fork");
break;
case 0: // proces potomny
sleep(2);
break;
default: // proces nadrzędny
waitpid(pid, NULL, 0);
test_errno("waitpid");
break;
}
/* kończymy proces, bez oglądania się na wątki */
return EXIT_SUCCESS;
}
Wyjście:
$ ./przyklad początek programu tworzenie 3 wątków w procesie 8228 uruchomiono wątek #1 wątek #1 w procesie #8228 uruchomiono wątek #2 wątek #2 w procesie #8228 uruchomiono wątek #3 wątek #3 w procesie #8228 wątek #1 w procesie #8228 wątek #2 w procesie #8228 wątek #3 w procesie #8228 fork => 8232 tworzenie 3 wątków w procesie 8232 fork => 0 uruchomiono wątek #1 wątek #1 w procesie #8232 uruchomiono wątek #2 wątek #2 w procesie #8232 uruchomiono wątek #3 wątek #3 w procesie #8232 wątek #1 w procesie #8228 wątek #3 w procesie #8228 wątek #2 w procesie #8228 wątek #1 w procesie #8232 wątek #2 w procesie #8232 wątek #3 w procesie #8232 wątek #1 w procesie #8228 wątek #2 w procesie #8228 wątek #3 w procesie #8228 wątek #1 w procesie #8232 wątek #2 w procesie #8232 wątek #3 w procesie #8232 wątek #1 w procesie #8228 wątek #3 w procesie #8228 wątek #2 w procesie #8228
Stopień współbieżności
edytujDostępne jeśli biblioteka implementuje opcję XSI.
Stopień współbieżności jest podpowiedzią (hint) dla biblioteki i ma znaczenie, jeśli wątki pthreads są uruchamiane na mniejszej liczbie wątków systemowych. Wówczas podpowiedź określa na ilu rzeczywistych wątkach zależy programowi.
W Linuxie jeden wątek pthreads odpowiada jednemu wątkowi systemowemu, więc ten parametr nie ma żadnego znaczenia.
Funkcja
edytuj- int pthread_setconcurrency(int new_level) (doc)
- ustawia nowy stopień współbieżności; jeśli 0, przyjmowany jest domyślny
- int pthread_getconcurrency(void) (doc)
- odczyt
Czas procesora zużyty przez wątek
edytujJeśli system implementuje timery (TMR) i rozszerzenie pthreads (TCT) dostępna jest funkcja pthread_getcpuclockid, zwracająca identyfikator zegara, który odmierza czas procesora zużyty przez wątek. Zdefiniowana jest wówczas także stała w time.h CLOCK_THREAD_CPUTIME_ID, która odpowiada identyfikatorowi zegara dla wywołującego wątku.
Makrodefinicja _POSIX_THREAD_CPUTIME informuje o istnieniu rozszerzenia.
Do odczytania czasu na podstawie identyfikatora zegara służy funkcja clock_gettime (z time.h).
Funkcja
edytuj- int pthread_getcpuclockid(pthread_t id, clockid_t *clock_id) (doc)
- odczyt identyfikatora zegara
Szkic użycia
edytuj#include <pthread.h>
#include <time.h>
pthread_t id_watku;
struct timespec czas; // z time.h
clock_t id_zegara; // z time.h
#ifdef _POSIX_THREAD_CPUTIME
if (pthread_getcpuclockid(id_watku, &id_zegara) == 0) {
if (clock_gettime(id_zegara, &czas) == 0) {
printf(
"czas procesora: %ld.%03ld ms\n",
czas.tv_sec, // tv_sec - sekundy
czas.tv_nsec/1000000 // tv_nsec - nanosekundy
);
}
else
/* błąd */
}
else
/* błąd */
#else
# error "pthread_getcpuclockid niedostępne w tym systemie"
#endif
Przykład
edytuj#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h> // sleep
#include <string.h> // strerror
#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)
// funkcja zwraca czas w milisekundach dla wskazanego zegara
long clock_ms(const clockid_t id_zegara);
// funkcja zwraca czas CPU dla wątku (w milisekundach)
long get_thread_time(pthread_t id);
/* parametry wątku */
typedef struct {
int id; // numer
int n; // liczba iteracji
} parametry;
// funkcja wątku
void* watek(void* _arg) {
parametry* arg = (parametry*)_arg;
int i;
printf("wątek #%d uruchomiony, dwa razy wykona %d pustych pętli\n",
(int)arg->id,
(int)arg->n
);
for (i=0; i < arg->n; ++i)
/* zużycie czasu procesora */;
sleep(2);
for (i=0; i < arg->n; ++i)
/* zużycie czasu procesora */;
/* podsumowanie pracy */
printf("wątek #%d zakończony, zużył %ldms czasu procesora\n",
(int)arg->id,
clock_ms(CLOCK_THREAD_CPUTIME_ID)
);
return NULL;
}
//------------------------------------------------------------------------
#define N 10 // liczba wątków
int main() {
pthread_t id[N];
parametry param[N];
int i;
srand(time(NULL));
printf("początek programu, uruchomianie zostanie %d wątków\n", N);
/* utworzenie wątku */
for (i=0; i < N; ++i) {
param[i].id = i;
param[i].n = rand() % 100000000 + 1;
errno = pthread_create(&id[i], NULL, watek, ¶m[i]);
test_errno("pthread_create");
}
/* stan na mniej więcej półmetku */
sleep(1);
puts("po około sekundzie wątki zużyły:");
for (i=0; i < N; ++i)
printf("* #%d: %ldms\n", i, get_thread_time(id[i]));
/* oczekiwanie na zakończenie wątków */
for (i=0; i < N; ++i) {
errno = pthread_join(id[i], NULL);
test_errno("pthread_join");
}
/* jeszcze podsumowanie */
puts("");
printf("główny wątek zużył %ldms czasu procesora\n", clock_ms(CLOCK_THREAD_CPUTIME_ID));
printf("proces zużył %ldms czasu procesora\n", clock_ms(CLOCK_PROCESS_CPUTIME_ID));
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------
long get_thread_time(pthread_t id) {
clockid_t id_zegara;
errno = pthread_getcpuclockid(id, &id_zegara);
test_errno("pthread_getcpuclockid");
return clock_ms(id_zegara);
}
//------------------------------------------------------------------------
long clock_ms(const clockid_t id_zegara) {
struct timespec czas;
if (clock_gettime(id_zegara, &czas)) {
perror("clock_gettime");
exit(EXIT_FAILURE);
}
else
return (czas.tv_sec * 1000) +
(czas.tv_nsec/1000000);
}
//------------------------------------------------------------------------
Wynik na maszynie dwuprocesorowej:
$ ./przyklad początek programu, uruchomianie zostanie 10 wątków wątek #0 uruchomiony, dwa razy wykona 86832212 pustych pętli wątek #7 uruchomiony, dwa razy wykona 8298184 pustych pętli wątek #9 uruchomiony, dwa razy wykona 67891648 pustych pętli wątek #5 uruchomiony, dwa razy wykona 27931234 pustych pętli wątek #8 uruchomiony, dwa razy wykona 23876946 pustych pętli wątek #3 uruchomiony, dwa razy wykona 52231547 pustych pętli wątek #6 uruchomiony, dwa razy wykona 16183104 pustych pętli wątek #4 uruchomiony, dwa razy wykona 87068047 pustych pętli wątek #1 uruchomiony, dwa razy wykona 59202170 pustych pętli wątek #2 uruchomiony, dwa razy wykona 48470151 pustych pętli po około sekundzie wątki zużyły: * #0: 209ms * #1: 138ms * #2: 119ms * #3: 125ms * #4: 209ms * #5: 67ms * #6: 41ms * #7: 17ms * #8: 59ms * #9: 154ms wątek #7 zakończony, zużył 39ms czasu procesora wątek #6 zakończony, zużył 80ms czasu procesora wątek #8 zakończony, zużył 117ms czasu procesora wątek #5 zakończony, zużył 135ms czasu procesora wątek #2 zakończony, zużył 232ms czasu procesora wątek #3 zakończony, zużył 251ms czasu procesora wątek #1 zakończony, zużył 285ms czasu procesora wątek #9 zakończony, zużył 323ms czasu procesora wątek #0 zakończony, zużył 423ms czasu procesora wątek #4 zakończony, zużył 418ms czasu procesora główny wątek zużył 0ms czasu procesora proces zużył 2307ms czasu procesora