Przejdź do zawartości

POSIX Threads/Specjalne działania

Z Wikibooks, biblioteki wolnych podręczników.

Stos funkcji finalizujących (cleanup)

[edytuj]

Z 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

[edytuj]

W 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

[edytuj]

W 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

[edytuj]

W 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

[edytuj]

Czasem 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

[edytuj]

Gdy 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

[edytuj]

Pthreads 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

Wysyłanie sygnałów do wątków

[edytuj]

Wysył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.

Funkcje

[edytuj]
  • int pthread_kill(pthread_t id, int signum)  (doc)
    • id - wątek
    • signum - numer sygnału

Przykład

[edytuj]

W 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

[edytuj]

Wskazany 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:

  1. Asynchroniczny - przerwanie następuje natychmiast, w dowolnym momencie.
  2. 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

[edytuj]

Przykładowy program uruchamia trzy wątki z różnymi ustawieniami dotyczącymi przerywania:

  1. dopuszcza przerwanie asynchroniczne,
  2. dopuszcza przerwanie opóźnione,
  3. 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

[edytuj]

Biblioteka 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

[edytuj]

W 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

[edytuj]

Dostę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

[edytuj]

Jeś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, &param[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