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.

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.

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.

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ą.

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'