POSIX Threads/Atrybuty wątku

Wstęp

edytuj

Atrybuty wątku pozwalają ustawić szereg parametrów wątków podczas ich tworzenia. Pojedynczy obiekt atrybutów może być wykorzystany wielokrotnie do tworzenia różnych wątków.

Wszystkie atrybuty wątku opisuje typ pthread_attr_t. Nazwy funkcji operujących na tym typie zaczynają się od pthread_attr. Funkcje ustawiające poszczególne atrybuty zaczynają się od pthread_attr_set i istnieją dla nich odpowiedniki odczytujące pthread_attr_get.

Inicjalizacja

edytuj

Przed użyciem atrybutu zmienna musi zostać zainicjalizowana funkcją pthread_attr_init, zwolnienie zasobów z nim związanych realizuje funkcja pthread_attr_destroy.

  • pthread_attr_t

Funkcje

edytuj
  • int pthread_attr_init(pthread_attr_t *attr)  (doc)
  • int pthread_attr_destroy(pthread_attr_t *attr)  (doc)

Przykład

edytuj
pthread_attr_t atrybuty;

pthread_attr_init(&atrybuty);
/* ... */
pthread_attr_destroy(&atrybuty);

Rodzaj wątku

edytuj

Rodzaje wątków zostały dokładniej opisane w innej sekcji. Funkcja pthread_attr_setdetachstate ustawia, natomiast funkcja pthread_attr_getdetachstate odczytuje rodzaj wątku, jaki ma zostać ustalony przy jego tworzeniu. Rodzaj jest identyfikowany jedną z wartości:

  • PTHREAD_CREATE_JOINABLE - utworzenie wątku typu joinable (domyślnie);
  • PTHREAD_CREATE_DETACHED - utworzenie wątku typu detached.

Funkcje

edytuj
  • int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)  (doc)
ustawienie rodzaju
  • int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate)  (doc)
odczyt

Rozmiar i adres stosu

edytuj

Domyślny rozmiar stosu wątku zależy od implementacji i może być rzędu kilku-kilkudziesięciu kilobajtów lub kilku megabajtów. Należy liczyć się z tym, że rozmiar będzie wewnętrznie zaokrąglony do rozmiaru strony pamięci (PAGE_SIZE - 4kB na procesorach x86). Zmiana rozmiaru jest możliwa jeżeli biblioteka pthreads implementuje opcję TSS.

Samodzielne ustalenie rozmiaru stosu może być konieczne, gdy funkcja wątku tworzy duże obiekty na stosie lub przeciwnie - gdy wiadomo, że wątki nie potrzebują zbyt wiele pamięci, a jednocześnie będzie potrzebna dużą ich liczba. Rozmiar stosu nie może być mniejszy od stałej PTHREAD_STACK_MIN (z pliku limits.h) ani przekraczać możliwości systemu.

Jeśli biblioteka implementuje opcję TSA można również ustalić adres stosu.

Funkcja

edytuj
  • int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)  (doc)
ustalenie nowego rozmiaru stosu stacksize
  • int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)  (doc)
odczyt rozmiaru
  • int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr)  (doc)
ustalenie nowego adresu stosu stackaddr
  • int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr)  (doc)
odczyt adresu
  • int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize)  (doc)
jednoczesne ustalenie adresu i rozmiaru stosu
  • int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize)  (doc)
odczyt adresu i rozmiaru

Przykład

edytuj

W programie uruchamiany jest wątek, który na stosie alokuje względnie dużą tablicę (ok. 200kB), następnie tablica jest czyszczona. Program z linii poleceń odczytuje żądany rozmiar stosu - ustawiając jego wartość na zbyt małą z pewnością doprowadzimy do błędu SIGSEGV.

#define _POSIX_C_SOURCE 200809L

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <limits.h>
#include <errno.h>

#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)

#define N (100*1024)

/* wątek używa sporej tablicy alokowanej na stosie */
void* watek(void* arg) {
	char tablica[N];
	int i;
	for (i=0; i < N; i++)
		tablica[i] = 0;

	return NULL;
}
//------------------------------------------------------------------------

int main(int argc, char* argv[]) {
	pthread_t	id;
	pthread_attr_t	attr;
	size_t		rozmiar;

	errno = pthread_attr_init(&attr);
	if (errno) {
		perror("pthread_attr_init");
		return EXIT_FAILURE;
	}

	if (argc > 1) {
		rozmiar = atoi(argv[1]);
		printf("rozmiar stosu ustalony przez użytkownika: %u\n", rozmiar);
		printf("minimalny rozmiar stosu: %u\n", PTHREAD_STACK_MIN);

		errno = pthread_attr_setstacksize(&attr, rozmiar);
		test_errno("pthread_attr_setstacksize");
	}
	else {
		pthread_attr_getstacksize(&attr, &rozmiar);
		printf("domyślny rozmiar stosu: %u\n", rozmiar);
	}

	errno = pthread_create(&id, &attr, watek, NULL);
	test_errno("pthread_create");

	pthread_join(id, NULL);
	puts("wątek zakończony");

	pthread_attr_destroy(&attr);
}
//------------------------------------------------------------------------

Obszar zabezpieczający stosu

edytuj

Opcja XSI. Jeśli rozmiar obszaru zabezpieczającego (guard) jest większy do zera, za stosem wątku rezerwowana jest pamięć (o rozmiarze zaokrąglonym w górę do rozmiaru strony, tj. PAGE_SIZE), która nie może być zapisywana ani odczytywana. Ułatwia to detekcję części powszechnych błędów polegających na wyjściu poza stos, czyli np. jego przepełnienie, są bowiem sygnalizowane przez sygnał SIGSEGV.

Domyślnie obszar ten jest włączony i ma minimalną wielkość.

Funkcje

edytuj
  • int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize)  (doc)
ustawienie rozmiaru obszaru zabiezpieczającego
  • int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize)  (doc)
jego odczyt

Szeregowanie wątków

edytuj

Wątki, podobnie jak procesy, mogą działać z różnymi priorytetami i być szeregowane przez różne algorytmy. Jest to opcja standardu TPS.

Dziedzicznie ustawień

edytuj

Wątek tworzony funkcją pthread_create może albo dziedziczyć ustawienia szeregowania z wywołującego wątku, albo uwzględniać wartości z atrybutów. Ten parametr opisują dwie wartości:

  • PTHREAD_INHERIT_SCHED - ustawienia dziedziczone,
  • PTHREAD_EXPLICIT_SCHED - ustawienia odczytywane z atrybutów.

Funkcje

edytuj
  • int pthread_attr_getinheritsched(const pthread_attr_t *attr, int *inheritsched)  (doc)
odczytanie parametru
  • int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched)  (doc)
ustawienie parametru

Wybór algorytmu szeregowania

edytuj

Wartość określająca algorytm szeregowania przyjmuje wartości:

  • SCHED_OTHER (domyślnie),
  • SCHED_FIFO,
  • SCHED_RR,
  • SCHED_SPORADIC (tylko jeśli dostępna jest opcja TSP).

Funkcje

edytuj
  • int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy)  (doc)
ustawienie algorytmu
  • int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *policy)  (doc)
odczyt

Wybór algorytmu szeregowania i ustawienie jego parametrów (m.in. priorytet)

edytuj

Oprócz algorytmu szeregowania można również określić jego parametry, w szczególności priorytet wątku.

Parametry algorytmu opisuje struktura sched_param (z sched.h), która zawiera co najmniej jedno pole określające priorytet:

struct sched_param {
	int sched_priority; /* priorytet */
};

Jeśli biblioteka implementuje opcję TPS (algorym SCHED_SPORADIC), struktura zawiera więcej pól.

Wartość priorytetu jest ograniczona wartościami zwracanymi przez funkcje sched_get_priority_min  (doc)/max  (doc), które zależą od wybranego algorytmu szeregowania.

Funkcje

edytuj
  • int pthread_setschedparam(pthread_t thread, int policy, const struct sched_param *param)  (doc)
ustawienie algorytmu i jego parametrów
  • int pthread_getschedparam(pthread_t thread, int *policy, struct sched_param *param)  (doc)
odczyt


odczytane parametry nie uwzględniają chwilowych zmian priorytetów, np. spowodowanych ochroną priorytetów w sekcjach krytycznych.

Przykład

edytuj

Program pozwala wybrać algorytm szeregowania, po czym tworzy kilka wątków z coraz większymi priorytetami (z dopuszczalnego zakresu).

#define _XOPEN_SOURCE	500
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <errno.h>
#include <unistd.h>

#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)

typedef struct {
	int	licznik;
	char	przerwij;
	int	priorytet;
} Arg;

/* funkcja wykonywana w wątku - zwiększa licznik */
void* watek(void* _arg) {
	Arg *arg = (Arg*)_arg;

	arg->licznik = 0;
	while (!arg->przerwij) {
		arg->licznik += 1;
		usleep(10);
	}
	
	return NULL;
}
//------------------------------------------------------------------------

#define N 4	/* liczba wątków */

int main(int argc, char* argv[]) {
	pthread_t id[N];
	pthread_attr_t attr;
	Arg arg[N];
	int pmin, pmax;
	int i, sched_policy;
	struct sched_param sp;

	sched_policy = SCHED_OTHER;
	if (argc > 1)
		switch (atoi(argv[1])) {
			case 0:
				sched_policy = SCHED_OTHER;
				break;
			case 1:
				sched_policy = SCHED_RR;
				break;
			case 2:
				sched_policy = SCHED_FIFO;
				break;
		}
	else {
		puts("program [0|1|2]");
		return EXIT_FAILURE;
	}

	pmin = sched_get_priority_min(sched_policy);
	pmax = sched_get_priority_max(sched_policy);
	switch (sched_policy) {
		case SCHED_OTHER:
			printf("SCHED_OTHER: priorytety w zakresie %d ... %d\n", pmin, pmax);
			break;
		case SCHED_RR:
			printf("SCHED_RR: priorytety w zakresie %d ... %d\n", pmin, pmax);
			break;
		case SCHED_FIFO:
			printf("SCHED_FIFO: priorytety w zakresie %d ... %d\n", pmin, pmax);
			break;
	}

	errno = pthread_attr_init(&attr);
	test_errno("pthread_attr_init");

	/* parametry szeregowania odczytywane z atrybutów  */
	errno = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
	test_errno("pthread_attr_setinheritsched");

	/* wybór podanego algorytmu szeregowania */
	errno = pthread_attr_setschedpolicy(&attr, sched_policy);
	test_errno("pthread_attr_setschedpolicy");

	/* utworzenie kilku wątków wątku z różnymi priorytetami */
	for (i=0; i < N; i++) {
		/* kolejne wątki mają coraz wyższe priorytety */
		sp.sched_priority = pmin + (pmax-pmin) * i/(float)(N-1);
		arg[i].przerwij   = 0;
		arg[i].licznik    = 0;
		arg[i].priorytet  = sp.sched_priority;

		/* ustawienie priorytetu */
		errno = pthread_attr_setschedparam(&attr, &sp);
		test_errno("pthread_attr_setschedparam");

		/* uruchomienie wątku */
		errno = pthread_create(&id[i], &attr, watek, &arg[i]);
		test_errno("pthread_create");

		printf("utworzono wątek #%d o priorytecie %d\n", i, arg[i].priorytet);
	}

	errno = pthread_attr_destroy(&attr);
	test_errno("pthread_attr_destroy");

	/* oczekiwanie */
	sleep(2);

	/* ustawienie flagi zakończenia pracy, którą testują funkcje wątków 
	   oraz odczyt ich bieżących liczników */
	for (i=0; i < N; i++) {
		arg[i].przerwij = 1;
		printf("wątek #%d (priorytet %3d): licznik = %d\n",
			i,
			arg[i].priorytet,
			arg[i].licznik
		);
	}

	/* teraz oczekiwanie na ich zakończenie */
	for (i=0; i < N; i++) {
		errno = pthread_join(id[i], NULL);
		test_errno("pthread_join");
	}

	return EXIT_SUCCESS;
}
//------------------------------------------------------------------------

Przykładowe wyjście w systemie Linux dla dostępnych algorytmów szeregowania.

$ ./przyklad 0
SCHED_OTHER: priorytety w zakresie 0 ... 0
utworzono wątek #0 o priorytecie 0
utworzono wątek #1 o priorytecie 0
utworzono wątek #2 o priorytecie 0
utworzono wątek #3 o priorytecie 0
wątek #0 (priorytet   0): licznik = 30630
wątek #1 (priorytet   0): licznik = 30631
wątek #2 (priorytet   0): licznik = 30633
wątek #3 (priorytet   0): licznik = 30620

$ ./przyklad 1
SCHED_RR: priorytety w zakresie 1 ... 99
utworzono wątek #0 o priorytecie 1
utworzono wątek #1 o priorytecie 33
utworzono wątek #2 o priorytecie 66
utworzono wątek #3 o priorytecie 99
wątek #0 (priorytet   1): licznik = 146812
wątek #1 (priorytet  33): licznik = 150084
wątek #2 (priorytet  66): licznik = 151116
wątek #3 (priorytet  99): licznik = 150744

$ ./przyklad 2
SCHED_FIFO: priorytety w zakresie 1 ... 99
utworzono wątek #0 o priorytecie 1
utworzono wątek #1 o priorytecie 33
utworzono wątek #2 o priorytecie 66
utworzono wątek #3 o priorytecie 99
wątek #0 (priorytet   1): licznik = 146659
wątek #1 (priorytet  33): licznik = 149249
wątek #2 (priorytet  66): licznik = 150764
wątek #3 (priorytet  99): licznik = 150895

Zakres konkurowania wątków

edytuj

Pthreads pozwala opcjonalnie określić, czy szeregowanie wątków będzie wykonywane w obrębie całego systemu (tzn. ze wszystkimi innymi wątkami i procesami), czy tylko w obrębie wątków z jednego procesu. Jest to opcja standardu TPS.

Stałe określające zakres konkurowania:

  • PTHREAD_SCOPE_SYSTEM - system,
  • PTHREAD_SCOPE_PROCESS - proces.

Funkcje

edytuj
  • int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope)  (doc)
ustawienie zakresu
  • int pthread_attr_getscope(const pthread_attr_t *attr, int *contentionscope)  (doc)
odczyt

Przykład

edytuj

Poniższy program wyświetla wszystkie informacje nt. atrybutów wątku.

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <limits.h>	// PTHREAD_STACK_MIN
#include <errno.h>

#define test_errno(msg) do{if (errno) {perror(msg); exit(EXIT_FAILURE);}} while(0)

void wyswietl_atrybuty(const pthread_attr_t* attr) {
	int x;
	size_t rozmiar;
	void* addr;
	struct sched_param param;

	puts("atrybuty wątku");

	// rodzaj wątku
	printf("* rodzaj: ");
	errno = pthread_attr_getdetachstate(attr, &x);
	test_errno("pthread_attr_getdetachstate");

	switch (x) {
		case PTHREAD_CREATE_JOINABLE:
			puts("joinable");
			break;
		case PTHREAD_CREATE_DETACHED:
			puts("detached");
			break;
		default:
			puts("???");
	}

	// adres i rozmiar stosu
	errno = pthread_attr_getstackaddr(attr, &addr);
	test_errno("pthread_attr_getstackaddr");
	printf("* adres stosu: %p\n", addr);

	errno = pthread_attr_getstacksize(attr, &rozmiar);
	test_errno("pthread_attr_getstacksize");
	printf("* rozmiar stosu: %d (minimalny %d)\n", rozmiar, PTHREAD_STACK_MIN);

	// rozmiar obszaru zabezpieczającego stosu
	errno = pthread_attr_getguardsize(attr, &rozmiar);
	test_errno("pthread_attr_getguardsize");
	printf("* rozmiar obszaru zabezpieczającego: %d\n", rozmiar);

	// szeregowanie
	errno = pthread_attr_getinheritsched(attr, &x);
	test_errno("pthread_attr_getinheritsched");
	switch (x) {
		case PTHREAD_INHERIT_SCHED:
			puts("* parametry szeregowania dziedziczone");
			break;

		case PTHREAD_EXPLICIT_SCHED:
			puts("* parametry podawane bezpośrednio");

			//
			printf("   - algorytm szeregowania: ");
			errno = pthread_attr_getschedpolicy(attr, &x);
			test_errno("pthread_attr_getschedpolicy");
			switch (x) {
				case SCHED_OTHER:
					puts("SCHED_OTHER");
					break;
				case SCHED_RR:
					puts("SCHED_RR");
					break;
				case SCHED_FIFO:
					puts("SCHED_FIFO");
					break;
				default:
					puts("???");
			}

			//
			errno = pthread_attr_getschedparam(attr, &param);
			test_errno("pthread_attr_getschedparam");
			printf("   - priorytet: %d\n", param.sched_priority);
			break;

		default:
			puts("???");
	}

	// zakres szeregowania
	errno = pthread_attr_getscope(attr, &x);
	test_errno("pthread_attr_getscope");

	printf("* zakres szeregowania: ");
	switch (x) {
		case PTHREAD_SCOPE_PROCESS:
			puts("proces");
			break;
		case PTHREAD_SCOPE_SYSTEM:
			puts("system");
			break;
		default:
			puts("???");
	}
}
//------------------------------------------------------------------------

int main() {
	pthread_attr_t attr;

	errno = pthread_attr_init(&attr);
	test_errno("pthread_attr_init");

	wyswietl_atrybuty(&attr);
	return EXIT_SUCCESS;
}
//------------------------------------------------------------------------

Przykładowe wyjście dla domyślnych ustawień atrybutów (w systemie Linux):

$ ./przyklad
atrybuty wątku
* rodzaj: joinable
* adres stosu: (nil)
* rozmiar stosu: 8388608 (minimalny 16384)
* rozmiar obszaru zabezpieczającego: 4096
* parametry szeregowania dziedziczone
* zakres szeregowania: system