POSIX Threads/Podstawowe operacje
Tworzenie wątku
[edytuj]W programie korzystającym z biblioteki pthreads, tuż po uruchomieniu działa dokładnie jeden wątek, wykonujący funkcję main.
Aby utworzyć nowe wątki programista podaje funkcję (dokładniej: adres funkcji), która ma zostać w nim wykonana - pthread_create tworzy i od razu uruchamia wątek, oraz zwraca jego identyfikator. Oczywiście jedna funkcja może być wykorzystywana do utworzenia wielu wątków. Funkcja użytkownika przyjmuje i zwraca wartość typu void*, interpretacja tych danych jest zależna od programu, pthreads nie narzuca więc tutaj żadnych ograniczeń. Wątki mogą być tworzone z poziomu dowolnego innego wątku.
Wątek identyfikuje wartość pthread_t, która jest argumentem dla większości funkcji bibliotecznych.
Liczba wątków, jakie można utworzyć jest ograniczona przez stałą PTHREAD_THREADS_MAX (z pliku limits.h) oraz rzecz jasna przez zasoby systemowe.
W Cygwinie PTHREAD_THREADS_MAX nie jest zdefiniowane, ponieważ system MS Windows pozwala utworzyć dowolną liczbę wątków |
Typy
[edytuj]- pthread_t
- identyfikator wątku
- pthread_attr_t
- atrybuty wątku
Funkcje
[edytuj]- int pthread_create(pthread_t *id, const pthread_attr_t *attr, void* (*fun)(void*), void* arg) (doc)
- id - identyfikator wątku;
- attr - wskaźnik na atrybuty wątku, określające szczegóły dotyczące wątku; można podać NULL, wówczas zostaną użyte domyślne wartości;
- fun - funkcja wykonywana w wątku; przyjmuje argument typu void* i zwraca wartość tego samego typu;
- arg - przekazywany do funkcji.
Przykład
[edytuj]Poniższy program typu hello world tworzy (w wątku głównym) kilka innych wątków, wykonujących funkcję watek i czeka na ich zakończenie. Używane są domyślne atrybuty wątku.
#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)
/* prosta funkcja wykonywana w wątku */
void* watek(void* _arg) {
puts("Witaj w równoległym świecie!");
return NULL;
}
//------------------------------------------------------------------------
#define N 5 /* liczba wątków */
int main() {
pthread_t id[N];
int i;
/* utworzenie kilku wątków wątku */
for (i=0; i < N; i++) {
errno = pthread_create(&id[i], NULL, watek, NULL);
test_errno("Nie powiodło się pthread_create");
}
/* oczekiwanie na zakończenie wszystkich wątków */
for (i=0; i < N; i++) {
errno = pthread_join(id[i], NULL);
test_errno("pthread_join");
}
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------
Identyfikator wątku
[edytuj]Wewnętrzna struktura typu pthread_t (podobnie jak innych używanych w pthreads) jest określona przez implementację. Jedyną operacją, jaką można wykonać na identyfikatorach jest ich porównanie ze względu na równość funkcją pthread_equal.
Funkcja pthread_self zwraca identyfikator wywołującego wątku, co czasem jest przydatne.
Funkcje
[edytuj]- int pthread_equal(pthread_t id1, pthread_t id2) (doc)
- stwierdzenie, czy identyfikatory wątków są równe
- pthread_t pthread_self() (doc)
- zwraca identyfikator wywołującego wątku
Kończenie wątku
[edytuj]Wątek jest kończony w chwili, gdy funkcja użytkownika przekazana w pthread_create zwraca sterowanie do wywołującego ją kodu, a więc w miejscu wystąpienia instrukcji return.
Ponadto istnieje funkcja pthread_exit, która powoduje zakończenie wątku - może zostać użyta w funkcjach wywoływanych z funkcji wątku.
Przykład
[edytuj]W przykładowym programie zakończenie wątku powoduje wywołanie pthread_exit na 6. poziomie zagnieżdżenia funkcji koniec_watku.
#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)
void koniec_watku(int licznik, int limit) {
int i;
for (i=0; i < licznik; i++) putchar(' ');
printf("licznik = %d, limit = %d\n", licznik, limit);
if (licznik == limit)
/* zakończenie wątku w którego kontekście wykonywana jest ta funkcja */
pthread_exit(NULL);
else
koniec_watku(licznik+1, limit);
}
//------------------------------------------------------------------------
void* watek(void* arg) {
koniec_watku(0, 5);
return NULL;
}
//------------------------------------------------------------------------
int main() {
pthread_t id;
/* utworzenie wątku */
errno = pthread_create(&id, NULL, watek, NULL);
test_errno("pthread_create");
/* oczekiwanie na jego zakończenie */
errno = pthread_join(id, NULL);
test_errno("pthread_join");
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------
Wyjście:
$ ./przyklad licznik = 0, limit = 5 licznik = 1, limit = 5 licznik = 2, limit = 5 licznik = 3, limit = 5 licznik = 4, limit = 5 licznik = 5, limit = 5
Oczekiwanie na zakończenie wątku
[edytuj]Oczekiwanie na zakończenie wątku jest dodatkowym sposobem synchronizacji między wątkami i dotyczy wyłącznie wątków typu joinable (zobacz Rodzaje wątków poniżej). Wątek wywołujący pthread_join zostaje wstrzymany do chwili, gdy wskazany wątek zakończy działanie - wówczas wątek oczekujący jest kontynuowany, a pthread_join przekazuje wartość wynikową zwróconą przez wątek. Jeśli wątek został przerwany wartością zwracaną jest stała PTHREAD_CANCELED.
Uwaga!
|
Oczekiwanie na zakończenie wątku można uzyskać zmiennymi warunkowymi (dodatkowo bez ograniczań na rodzaj wątku), wymaga to jednak dodatkowej pracy ze strony programisty.
Funkcje
[edytuj]- int pthread_join(pthread_t id, void **retval) (doc)
- id - identyfikator wątku, którego zakończenie jest oczekiwane;
- retval - wskaźnik na wartość wynikową wątku; może być NULL, wówczas wynik jest ignorowany
Zakończenie procesu, a kończenie wątków
[edytuj]Kiedy kończy się proces, wszystkie wątki utworzone w jego obrębie są również (gwałtownie) kończone. Dlatego koniecznie trzeba użyć albo wbudowanego mechanizmu synchronizacji, albo jakiegoś własnego rozwiązania.
Rodzaje wątków
[edytuj]W pthreads wątki są dwojakiego rodzaju:
- joinable (domyślny rodzaj)
- detached
Rodzaj jest ustalany w atrybutach wątku. Można jednak po utworzeniu wątku zmienić typ z joinable na detached funkcją pthread_detach; odwrotne działanie nie jest możliwe.
Wątek typu joinable to taki, z którego można odczytać wartość wynikową zwróconą przez funkcję. Gdy wątek tego typu kończy się, jego zasoby nie są zwalniane do chwili wywołania funkcji pthread_join (patrz Oczekiwanie na zakończenie wątku).
Warto więc zwrócić uwagę, że utworzenie wątku typu joinable i nie wywołanie wspomnianej funkcji skutkować będzie wyciekiem pamięci (następstwem którego tworzenie nowych wątków może stać się niemożliwe).
Wątek typu detached z chwilą zakończenia działania od razu zwalnia wszystkie zasoby; funkcja pthread_join nie akceptuje identyfikatorów do wątków tego typu.
Funkcje
[edytuj]- int pthread_detach(pthread_t id) (doc)
- zmiana rodzaju wątku
Przekazywanie argumentów i zwracanie wyników
[edytuj]Przekazywanie argumentów
[edytuj]Funkcja wykonywana w wątku przejmuje argument typu void* podawany w funkcji pthread_create - wskaźnik ten może więc wskazywać dowolną strukturę, może być także pusty.
Uwaga!
|
Można również rzutować bezpośrednio typy, których rozmiar nie przekracza rozmiaru wskaźnik, tzn. gdy sizeof(typ) <= sizeof(void*); mogą to być typy int, short, char, być może też inne - zależnie od platformy sprzętowej i kompilatora.
Zwracanie wyniku
[edytuj]Funkcja użytkownika zwraca wskaźnik na void*. Jeśli potrzeba zwrócić jakieś dane, należy je zaalokować na stercie funkcją malloc lub podobną.
Uwaga!
|
Można również rzutować na void*, tak samo jak w przypadku przekazywania argumentów.
Przykład
[edytuj]#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)
typedef struct _Arg { // struktura argumentów dla wątku 1.
char napis[256];
int rok;
int mies;
int dzien;
} Argument;
void* watek1(void* _arg) {
Argument* arg = (Argument*)_arg;
printf("Witaj %s w dniu %04d-%02d-%02d\n",
arg->napis,
arg->rok,
arg->mies,
arg->dzien
);
return NULL;
}
//------------------------------------------------------------------------
/* wątek 2. zwraca pewien dynamicznie tworzony napis */
void* watek2(void* liczba) {
char* napis;
int i;
printf("Wątek 2 wywołany z argumentem liczbowym %d\n", (int)liczba);
napis = malloc((int)liczba + 1);
if (napis) {
for (i=0; i < (int)liczba; i++)
napis[i] = 'x';
napis[(int)liczba] = 0;
}
return napis;
}
//------------------------------------------------------------------------
int main() {
pthread_t w1, w2;
Argument arg;
char* wynik;
/* przygotowanie argumentów */
strcpy(arg.napis, "Wikibooks");
arg.rok = 2010;
arg.mies = 3;
arg.dzien = 14;
/* utworzenie dwóch wątków */
errno = pthread_create(&w1, NULL, watek1, &arg);
test_errno("pthread_create");
errno = pthread_create(&w2, NULL, watek2, (void*)27);
test_errno("pthread_create");
/* oczekiwanie na zakończenie obu */
errno = pthread_join(w1, NULL);
test_errno("pthread_join");
errno = pthread_join(w2, (void**)&wynik);
test_errno("pthread_join");
if (wynik) {
printf("wątek 2 zwrócił napis: '%s'\n", wynik);
free(wynik);
}
else
puts("wątek 2 nic nie zwrócił");
return EXIT_SUCCESS;
}
//------------------------------------------------------------------------
Wyjście:
$ ./przyklad Witaj Wikibooks w dniu 2010-03-14 Wątek 2 wywołany z argumentem liczbowym 27 wątek 2 zwrócił napis: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'