int21h

Vlákna v Linuxu

Tak jsem se po dlouhé době odhodlal k druhému dílu seriálu. Předchozí díl pojednával o procesech a signálech, teď se pustíme do vláken. V mnohých ohledech jsou vlákna dost podobná procesům - umožňují programu realizovat více činností najednou. Říká se jim také odlehčené procesy. Při tvorbě procesu se veškeré prostředí, proměnné, deskriptory zkopírují tomuto procesu. Ale všechna vlákna tvoří dohromady jeden proces. Pokud jedno vlákno například uzavře deskriptor nebo změní proměnnou, tak to ostatní vlákna a nadřízené procesy ovlivní. Jádro zpracovává vlákna dost zvláštním způsobem. Podle času a možnosti některá vlákna pozastavuje a jiná spouští. Je nepředpovídatelné, které vláknou zkončí dřív. V linuxu je pro práci s vlákny určena knihovna pthread, která je součástí glibc.

Každé vlákno má své vlastní id číslo. Číslo je normální int, ale používejte typ pthread_t kvůli přenositelnosti. Abychom si to zjednodušili - vlákno je vlastně funkce typu (void *), která může mít jeden parametr, opět (void *). Přes tento typ můžeme přenášet libovolné struktury. Pro programátory, kteří jsou zvyklí pracovat na dosu je asi těžké představit si víceúlohové aplikace. U procesů jsem to už vysvětloval a teď, jak se to s vlákny: Všechno to začíná na rodičovském procesu. Ten v průběhu může vytvořit vlákno. To znamená, že jádro začne zpracovávat novou funkci, ale pro proces to nic neznamená, protože normálně pokračuje dál. V tuto chvíli zpracovává systém dvě různé operace. Ty dohromady ovšem tvoří jeden proces. Takových vláken může vytvořit i více. Některá vlákna může proces ukončit, nebo počkat na jejich výsledek. Existují i takzvaná odloučená vlákna, která se od hlavního procesu odpojí a ten je pak už nemůže ovládat, ani se s nimi spojit. Pokud je vlákno odloučené, tak po skončení jádro uklidí všechny jeho zdroje. U spojitelných vláken je to podobné jako s procesy. Pokud je proces neukončí zůstanou v systému ve stavu podobném jako zombie. Tak teď už konečně příklad.

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>


void *detached(void *data) {
	while (1) {
		fprintf(stderr,".");
	}
}


void *mocnina(void *data) {
	int cislo=*((int *) data),vysledek=0;
	printf("Counting...\n");
	vysledek=cislo*cislo;
	return (void *) vysledek;
}


void *vlakno2(void *data) {
	while (1) {
		printf("x\n");
		sleep(2);
	}


}


int main() {
	pthread_t vlakno_id,vlakno2_id,vlakno3_id;
	pthread_attr_t atribut;
	int number=32,vystup=0;
	
	pthread_attr_init(&atribut);
	pthread_attr_setdetachstate(&atribut,PTHREAD_CREATE_DETACHED);
	
	pthread_create(&vlakno_id,NULL,&mocnina,&number);
	pthread_create(&vlakno2_id,NULL,&vlakno2,NULL);
	pthread_create(&vlakno3_id,&atribut,&detached,NULL);
	
	pthread_attr_destroy(&atribut);


	/*Tady to pokracuje
	  Priklad s mocninou je trivialni, ale splnuje ideu:
	  Pocitani nespomaluje hlavni proces
	*/
	pthread_cancel(vlakno2_id);
	printf("Second thread canceled.\n");
	
	pthread_join(vlakno_id,(void *) &vystup);
	pthread_join(vlakno2_id,NULL);
	printf("%d^2 = %d\n",number,vystup);
	return 0;
}  

Takže na vysvětlení:
pthread_create() - vytvoří vlákno. První argument je id vlákna, druhý atributy, třetí ukazatel na funkci a čtvrtý argument.
pthread_join() - vzpomínáte na wait() u procesů? Je to přibližně to samé.
pthread_cancel() - zruší vlákno. V tomto případě lze použít i pthread_exit()
Atributů je kromě detached celá řada. Najdete je například v dokumentaci glibc.

U zrušení vás jistě napadlo: co se stane, když se vlákno přeruší uprostřed nějaké operace?
To je opravdu problém. Může nastat situace, kde si vlákno alokuje paměť, začne provádět operace a pak paměť dealokuje. Když přerušíme vlákno uprostřed činnosti, paměť zůstane alokována. Pro tyto případy jsou zavedeny takzvané kritické sekce.
Může to vypadat asi takhle:

void *funkce(void *data) {
	int *buffer,oldstatus;
	
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&oldstatus); /*kriticka sekce*/
	buffer=(int *)malloc(sizeof(int)*256);
	...
	free(buffer);
	pthread_setcancelstate(oldstatus,NULL);/*konec*/
}

Pokud program vstoupí do kritické sekce, ignoruje jakékoliv pokusy o zrušení. Všechny pokusy ale zaznamenává a ukončí se hned po opuštění kritické sekce.

Existuje ještě jedno řešení této situace - čistící funkce. To je vhodné pokud nepotřebujete vždy provést celý kód, ale zároveň zůstává problém uvolnění bufferu. Čistící funkce je právě například funkce, která provede uvolnění bufferu přes free(). Na začátku funkce nahodíme handler, který se stará o to, že při pokusu o zrušení provede určenou funkci. Na konci funkce vlákna zase handler odebereme.

Může to vypadat nějak takhle:

void cleanup(void *buffer) {
	free(buffer);
	printf("Buffer deallocated\n");
}


void *vlakno(void *data) {
	int *buffer=malloc(sizeof(int)*256);
	printf("Buffer allocated\n");
	pthread_cleanup_push(cleanup,buffer); /* instalace handleru */
	...
	pthread_cleanup_pop(1); /* deinstalace, popripade provedeni funkce */
}  

Pokud vlákno přerušíme, provede se funkce cleanup() ihned. Jinak se provede při zavolání pthread_cleanup_pop(), protože jsme předali nenulový parametr.

Vlákna mají ještě jeden velký problém. Pokud víc vláken pracuje s jednou datovou strukturou, může se stát, že budou do jedné struktury zapisovat dvě vlákna současně. To spadá do takzvaného problému souběhu. Řeší to zařízení mutex - MUTual EXclusion device. Mutex má dva stavy - odemknutý a zamknutý. Pro příklad: vlákno začno zpracovávat strukturu a zamkne mutex. Druhé vlákno se pokusí pracovat s touto strukturou. Zjistí ale, že mutex je už zamklý a zablokuje se. První vlákno dokončí svou činnost a mutex odemkne. To probudí druhé vlákno, které začné strukturu zpracovávat. Četl jsem hodně milé přirovnání, že mutex je jako dveře od toalety. Vlákno se zamkne a ostatní musí čekat dokud se nedostane ven.

V praxi to vypadá asi takhle:

pthread_mutex_t our_mutex=PTHREAD_MUTEX_INITIALIZER;


void *func(void *data) {
	pthread_mutex_lock(&our_mutex);
	...
	pthread_mutex_unlock(&our_mutex);
}  

Jinak, mutex je velmi rozsáhlá kapitola sama o sobě. Doporučuju najít si něco na googlu pro další studium.


Literatura
Pokročilé programování v operačním systému Linux
The GNU C Library documentation

2006-12-22 | BOby
Reklamy:
„Rozdávat rady je zbytečné. Moudrý si poradí sám a hlupák stejně neposlechne.“ Mark Twain