Linux a vlßkna |
Vladimφr Michl, 7. srpna 1998 |
Tento Φlßnek si klade za ·kol seznßmit Φtenß°e s vlßkny v Linuxu
a jejich pou╛itφm, p°φpadn∞ s tφm, Φeho by se m∞l Φlov∞k p°i pou╛φvßnφ
vlßken vyvarovat.
Ale od zaΦßtku. Nejprve je t°eba osv∞tlit rozdφl mezi termφnem proces
a vlßkno.
Jako proces je v systΘmu chßpßn souhrn k≤du programu, dat programu,
zßsobnφku, ·daj∙ o procesem otev°en²ch souborech, a takΘ informacφ
ohledn∞ zpracovßnφ signßl∙. Tyto v╣echny informace mß ka╛d² proces
vlastnφ (privßtnφ) a nem∙╛e je sdφlet s jin²m procesem, krom∞ datov²ch
oblastφ. P°i volßnφ jßdra fork(2) se pak tyto informace pro
nov² proces skopφrujφ, tak╛e jsou pro n∞j zase privßtnφ.
Jako vlßkno si m∙╛eme p°edstavit odlehΦen² proces, tj. pouze k≤d
vlßkna a zßsobnφk, v╣e ostatnφ je sdφleno s ostatnφmi vlßkny tΘho╛
procesu. Vlßkno je tedy podmno╛inou procesu a proces m∙╛e vlastnit
n∞kolik vlßken. Vlßkno samo o sob∞ v systΘmu existovat nem∙╛e, musφ
k n∞mu v╛dy existovat proces, se kter²m sdφlφ v╣echna data, otev°enΘ
soubory, zpracovßnφ signßl∙.
Pro implementaci vlßken existujφ tyto modely:
- one-to-one - Implementace provedena na ·rovni
jßdra. Ka╛dΘ vlßkno je pro jßdro samostatn² proces, plßnovaΦ proces∙
neΦinφ rozdφl mezi vlßknem a procesem. Nev²hodou tohoto modelu m∙╛e
b²t velkß re╛ie p°i p°epφnßnφ vlßken.
- many-to-one - Implementace provedena na ·rovni
u╛ivatele, program si sßm implementuje vlßkna a v╣e okolo. Jßdro
o vlßknech v procesech nemß ani tu╣enφ. Tento model se nehodφ na
vφceprocesorovΘ systΘmy, proto╛e vlßkna nemohou b∞╛et zßrove≥ (ka╛dΘ
na jinΘm procesoru), jeden proces nelze nechat vykonßvat na dvou
procesorech. V²hodou m∙╛e b²t malß re╛ie p°epφnßnφ vlßken.
- many-to-many - Implementace provedena na ·rovni jßdra
i u╛ivatele. Tento model eliminuje nev²hody p°edchozφch implementacφ
(velkß re╛ie p°i p°epφnßnφ proces∙, soub∞╛n∞ nem∙╛e b∞╛et vφce vlßken)
a je proto pou╛it v mnoha komerΦnφch UNIXech (Solaris, Digital Unix,
IRIX).
V Linuxu je pou╛it model prvnφ. Nev²hoda velkΘ re╛ie v podstat∞ nenφ,
proto╛e p°epφnßnφ proces∙ je v Linuxu implementovßno velmi
efektivn∞. Pro tvorbu proces∙ a vlßken se v Linuxu pou╛φvß volßnφ
jßdra clone(2), kterΘ ale pou╛φvajφ pouze knihovny
obhospoda°ujφcφ vlßkna.
V zaΦßtcφch, kdy se v Unixech zaΦala vlßkna objevovat, m∞l ka╛d²
unixov² operaΦnφ systΘm jinΘ aplikaΦnφ rozhranφ pro prßci s vlßkny,
a proto byly programy ╣patn∞ p°enositelnΘ. Proto vznikla norma POSIX,
kterß mimo jinΘ takΘ definuje aplikaΦnφ rozhranφ pro prßci s vlßkny
(POSIX 1003.1c). Toto POSIXovΘ rozhranφ je dostupnΘ i na OS
Solaris 2.5, Digital Unix 4.0, IRIX 6. S ka╛dou distribucφ Linuxu
postavenou na glibc-2.0 je dodßvßna knihovna pthread,
kterß prßv∞ toto POSIXovΘ aplikaΦnφ rozhranφ implementuje.
Pro vytvo°enφ a ukonΦenφ vlßkna lze pou╛φt nßsledujφcφ funkce:
int pthread_create(pthread_t * thread,
pthread_attr_t * attr,
void * (*start_routine)(void *), void * arg);
- funkce vytvo°φ novΘ vlßkno, kterΘ bude vykonßvat funkci start_routine, co╛ je funkce akceptujφcφ jeden parametr typu void *. Na adresu thread je ulo╛en identifikßtor vlßkna a jako
atributy vlßkna m∙╛eme uvΘst NULL pro implicitnφ hodnoty.
void pthread_exit(void *retval);
- tato funkce p°edΦasn∞ ukonΦφ vlßkno, ze kterΘho byla funkce zavolßna.
Vlßkno se takΘ ukonΦφ, skonΦφ-li funkce start_routine. V obou
p°φpadech se p°edßvß nßvratov² k≤d.
int pthread_join(pthread_t th,
void **thread_return);
- funkce Φekß na ukonΦenφ vlßkna th. Na adresu thread_return je ulo╛en nßvratov² k≤d vlßkna.
P°φklad, jak funkce pou╛φt, naleznete na v²pise P°φklad pou╛itφ vlßken.
#include <pthread.h>
#include <stdio.h>
#define ITEMS 10000
void * process(void *a){
int i;
printf("Process %s: start\n", (char *)a);
for (i = 0; i<ITEMS; i++){
printf("%s", (char *)a);
};
printf("Process %s: end\n", (char *)a);
return NULL;
}
int main(){
int retcode;
pthread_t a,b;
void * retval;
retcode = pthread_create(&a, NULL, process, "A");
if (retcode != 0) fprintf(stderr, "create a failed %d\n", retcode);
retcode = pthread_create(&b, NULL, process, "B");
if (retcode != 0) fprintf(stderr, "create b failed %d\n", retcode);
retcode = pthread_join(a, &retval);
if (retcode != 0) fprintf(stderr, "join a failed %d\n", retcode);
retcode = pthread_join(b, &retval);
if (retcode != 0) fprintf(stderr, "join b failed %d\n", retcode);
return 0;
}
|
V²pis Φ. 1: P°φklad pou╛itφ vlßken
P°i p°eklßdßnφ programu, kter² pou╛φvß vlßkna, je t°eba tomuto
p°izp∙sobit hlaviΦkovΘ soubory knihovny glibc tak, aby byly
reentrantnφ. To provedeme definovßnφm makra _REENTRANT.
Dßle je t°eba program slinkovat s knihovnou pthread.
Pro p°eklad programu na v²pise P°φklad pou╛itφ vlßken pou╛ijeme:
gcc -D_REENTRANT -o example1 example1.c -lpthread
Nejprve si polo╛me otßzku, co je to kritickß sekce. Za kritickou sekci
pova╛ujeme tu Φßst k≤du vlßkna, kterß operuje nad sdφlen²mi daty
a hrozφ, ╛e paraleln∞ m∙╛e jinΘ vlßkno operovat nad stejn²mi daty.
D∙sledkem m∙╛e b²t nekonzistence dat. Nap°φklad jedno vlßkno zv²╣φ
sdφlenou prom∞nnou A o jedna a dßle s nφ poΦφtß, kde╛to druhΘ vlßkno
prom∞nou A zmen╣φ o dv∞ a dßle s nφ poΦφtß. Pokud se po╣t∞stφ, tak se
instrukce mohou prolo╛it tak, ╛e ani jedno vlßkno nedß sprßvn²
v²sledek. Tomuto je t°eba zabrßnit a to tφm, ╛e do tΘ Φßsti, kterß
pracuje s prom∞nnou A m∙╛e vstoupit pouze jedno vlßkno, druhΘ musφ
Φekat a╛ to prvnφ skonΦφ. TakovΘto kritickΘ sekce, kde m∙╛e b²t
v jednom okam╛iku pouze jedno vlßkno, naz²vßme MUTEX (MUTual
EXclusion). Mutex mß dva stavy - zamΦen² (locked - n∞kterΘ
vlßkno je uvnit°) a odemΦen² (unlocked - v mutexu nikdo nenφ).
Pro prßci s mutexy pou╛ijeme funkce:
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
- zamΦenφ mutexu. Po nßvratu je mutex v╛dy zamΦen pro vlßkno,
kterΘ tuto funkci vykonalo. Pokud je mutex ji╛ zamΦen, funkce
pozastavφ vlßkno a Φekß na odemΦenφ mutexu, aby nßsledn∞ mutex zamkla
a mohla nechat vlßkno pokraΦovat.
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- pokus o zamΦenφ mutexu. Pokud je mutex ji╛ zamΦen, funkce se
vrßtφ s chybou EBUSY.
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
- uvoln∞nφ zdroj∙ spojen²ch s mutexem
V p°φkladu SchematickΘ znßzorn∞nφ pou╛itφ mutexu m∙╛ete vid∞t pou╛itφ mutexu.
pthread_mutex_t mut_var;
...
/* Inicializace mutexu */
pthread_mutex_init(&mut_var, NULL);
...
/* Vstup do mutexu */
pthread_mutex_lock(&mut_var);
/* Vykonßnφ operacφ nad sdφlen²mi daty */
...
/* V²stup z mutexu */
pthread_mutex_unlock(&mut_var);
...
/* Na konci programu zru╣enφ mutexu */
pthread_mutex_destroy(&mut_var);
|
V²pis Φ. 2: SchematickΘ znßzorn∞nφ pou╛itφ mutexu
U mutex∙ se m∙╛eme setkat s tφm, ╛e bude t°eba mutex zamknout
v zßvislosti na podmφnce. Nap°φklad problΘm producent - konzument.
Producent produkuje data do sdφlenΘ prom∞nnΘ a konzument je Φte.
P°itom prom∞nnß musφ b²t zabezpeΦena mutexem a zßrove≥ se musφ
hlφdat stav, zda prom∞nnß obsahuje u╛iteΦnß data.
I na toto POSIX myslφ, a to pomocφ nßsledujφcφch funkcφ:
int pthread_cond_init(pthread_cond_t *cond,
pthread_condattr_t *cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
- zp∙sobφ spu╣t∞nφ jednoho vlßkna, kterΘ Φekß na podmφnce.
Jestli╛e neΦekß ╛ßdnΘ vlßkno, funkce nemß ╛ßdn² efekt. ╚ekß-li
vφce vlßken, spustφ se pouze jedno, ale nenφ definovßno jakΘ.
int pthread_cond_broadcast(pthread_cond_t *cond);
- zp∙sobφ spu╣t∞nφ v╣ech vlßken Φekajφcφch na podmφnce. Jestli╛e
neΦekß ╛ßdnΘ vlßkno, funkce nemß ╛ßdn² efekt.
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
- automaticky odemkne mutex, pozastavφ vlßkno a Φekß na signßl od
podmφnky. Po p°φchodu signßlu je mutex uzamΦen a tato funkce
ukonΦena. Ka╛dß podmφnka musφ b²t uzav°ena v mutexu.
int pthread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
- je podobnΘ pthread_cond_wait() s tφm rozdφlem, ╛e Φekßnφ
je Φasov∞ omezeno. Pokud Φas vypr╣φ, pak je sekce uzamΦena
a funkce je ukonΦena s chybou ETIMEDOUT.
int pthread_cond_destroy(pthread_cond_t *cond);
- uvolnφ zdroje spojenΘ s podmφnkou
V p°φkladu Pou╛itφ mutexu v problΘmu producent - konzument m∙╛ete vid∞t pou╛itφ mutexu a podmφnek na
problΘmu producent - konzument. V╣imn∞te si rozdφlnΘ inicializace
podmφnek, samoz°ejm∞ ob∞ podmφnky jdou inicializovat stejn²m zp∙sobem.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define ITEMS 100
#define NONVALID 0
#define VALID 1
pthread_mutex_t mut_var;
pthread_cond_t condvalid;
pthread_cond_t condnonvalid = PTHREAD_COND_INITIALIZER;
int valid;
int share;
void * konzument(void *a){
printf("Process %s: start\n", (char *)a);
while(1){
pthread_mutex_lock(&mut_var);
if (!valid)
pthread_cond_wait(&condvalid, &mut_var);
valid = NONVALID;
printf("Process %s: %i\n", (char *)a, share);
if (share == -1){
pthread_mutex_unlock(&mut_var);
break;
};
pthread_cond_signal(&condnonvalid);
pthread_mutex_unlock(&mut_var);
};
printf("Process %s: end\n", (char *)a);
return NULL;
}
void * producent(void *a){
int i;
printf("Process %s: start\n", (char *)a);
for (i = 0; i<ITEMS; i++){
pthread_mutex_lock(&mut_var);
if (valid)
pthread_cond_wait(&condnonvalid, &mut_var);
share = (int)rand();
if (share == -1) share = 0;
if (i == ITEMS - 1) share = -1;
printf("Process %s: %i\n", (char *)a, share);
valid = VALID;
pthread_cond_signal(&condvalid);
pthread_mutex_unlock(&mut_var);
};
printf("Process %s: end\n", (char *)a);
return NULL;
}
int main(){
pthread_t a,b;
pthread_mutex_init(&mut_var, NULL);
pthread_cond_init(&condvalid, NULL);
pthread_create(&a, NULL, producent, "producent");
pthread_create(&b, NULL, konzument, "konzument");
pthread_join(a, NULL);
pthread_join(b, NULL);
pthread_cond_destroy(&condvalid);
pthread_cond_destroy(&condnonvalid);
pthread_mutex_destroy(&mut_var);
return 0;
}
|
V²pis Φ. 3: Pou╛itφ mutexu v problΘmu producent - konzument
Semafory se pou╛φvajφ pro podobn² ·Φel jako mutexy, a to pro
kontrolovßnφ vstupu do kritick²ch sekcφ. Ale na rozdφl od mutexu, kdy
v sekci m∙╛e b²t pouze jeden, se semafory lze docφlit, ╛e v sekci m∙╛e
b²t vφce vlßken. Semafor si m∙╛eme p°edstavit jako poΦφtadlo
s poΦßteΦnφ hodnotou, kterou nastavφ u╛ivatel. V╛dy p°i vstupu do
kritickΘ sekce se Φekß, dokud nenφ hodnota semaforu v∞t╣φ ne╛ nula.
Pokud je, pak se hodnota zmen╣φ o jednu a vstoupφ se do kritickΘ sekce.
Na konci sekce se hodnota semaforu o jedniΦku zvedne. Pro prßci
se semafory pou╛φvßme funkce:
int sem_init(sem_t *sem, int pshared,
unsigned int value);
- inicializace semaforu. Argument pshared urΦuje, zda je semafor
lokßlnφ pro tento proces (hodnota 0) nebo je sdφlen mezi procesy
(hodnota != 0). V Linuxu jsou podporovßny pouze lokßlnφ semafory.
int sem_wait(sem_t * sem);
- slou╛φ pro vstup do kritickΘ sekce. Pokud je sekce obsazena
(semafor == 0), pak se Φekß a╛ se sekce uvolnφ.
int sem_trywait(sem_t * sem);
- slou╛φ pro vstup do kritickΘ sekce. Je-li sekce obsazena,
funkce se vrßtφ s chybou EAGAIN.
int sem_post(sem_t * sem);
- slou╛φ k ukonΦenφ kritickΘ sekce.
int sem_getvalue(sem_t * sem, int * sval);
int sem_destroy(sem_t * sem);
- uvolnφ v╣echny zdroje spojenΘ se semaforem.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#define ITEMS 100
sem_t semfull;
sem_t semempty;
int share;
void * konzument(void *a){
printf("Process %s: start\n", (char *)a);
while(1){
sem_wait(&semfull);
printf("Process %s: %i\n", (char *)a, share);
if (share == -1){
sem_post(&semempty);
break;
};
sem_post(&semempty);
};
printf("Process %s: end\n", (char *)a);
return NULL;
}
void * producent(void *a){
int i;
printf("Process %s: start\n", (char *)a);
for (i = 0; i<ITEMS; i++){
sem_wait(&semempty);
share = (int)rand();
if (share == -1) share = 0;
if (i == ITEMS - 1) share = -1;
printf("Process %s: %i\n", (char *)a, share);
sem_post(&semfull);
};
printf("Process %s: end\n", (char *)a);
return NULL;
}
int main(){
pthread_t a,b;
sem_init(&semfull, 0, 0);
sem_init(&semempty, 0, 1);
pthread_create(&a, NULL, producent,
"producent");
pthread_create(&b, NULL, konzument,
"konzument");
pthread_join(a, NULL);
pthread_join(b, NULL);
sem_destroy(&semfull);
sem_destroy(&semempty);
return 0;
}
|
V²pis Φ. 4: Pou╛itφ semafor∙ u problΘmu producent - konzument
Toto rozhranφ pro semafory definuje norma POSIX 1003.1b a POSIX
1003.1i.
int pthread_cancel(pthread_t thread);
- vyvolß po╛adavek na zru╣enφ vlßkna.
int pthread_setcancelstate(int state,
int *oldstate);
- nastavφ chovßnφ vlßkna, kterΘ tuto funkci vyvolalo,
na po╛adavek jeho zru╣enφ. Mo╛nΘ jsou dva stavy: PTHREAD_CANCEL_ENABLE a PTHREAD_CANCEL_DISABLE.
int pthread_setcanceltype(int type, int *oldtype);
- nastavφ, kdy je mo╛no vlßkno zru╣it. Mo╛nΘ jsou dv∞ nastavenφ:
PTHREAD_CANCEL_ASYNCHRONOUS - vlßkno bude zru╣eno skoro
okam╛it∞ po p°ijetφ po╛adavku nebo PTHREAD_CANCEL_DEFERRED -
vlßkno se zru╣φ a╛ v okam╛iku, kdy dojde do bodu, kde je mo╛no vlßkno
zru╣it. Jako body jsou v POSIXu definovßny tyto funkce: pthread_join(3), pthread_cond_wait(3), pthread_cond_timedwait(3), pthread_testcancel(3), sem_wait(3), sigwait(3).
void pthread_testcancel(void);
- tato funkce pouze testuje, zda byl p°ijat po╛adavek na zru╣enφ
vlßkna. Pokud p°ijat byl, vlßkno je zru╣eno, v opaΦnΘm p°φpad∞ se
funkce normßln∞ vrßtφ. Funkce se pou╛φvß v mφstech, kde jsou dlouhΘ
kusy k≤du bez bod∙ vhodn²ch pro zru╣enφ.
Pokud je vlßkno v bodu vhodnΘm pro zru╣enφ (viz pthread_setcanceltype(3)) a p°ijalo po╛adavek na zru╣enφ, bude
zru╣eno. TotΘ╛ se stane, pokud p°ijalo po╛adavek na zru╣enφ a a╛
nßsledn∞ vejde do bodu vhodnΘho pro zru╣enφ (pouze p°i nastavenΘm PTHREAD_CANCEL_DEFERRED).
Pokud se ukonΦφ hlavnφ vlßkno, ani╛ by poΦkalo na vlßkna jφm
vytvo°enß, jsou tato vlßkna ukonΦena takΘ.
Jak pou╛φt funkce m∙╛ete vid∞t na p°φkladu UkonΦenφ vlßkna.
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <semaphore.h>
sem_t sem;
void * process(void *a){
printf("Proces entered\n");
sem_wait(&sem);
printf("Proces exiting\n");
return NULL;
}
int main(){
pthread_t thid;
void * thretval;
sem_init(&sem, 0, 0);
pthread_create(&thid, NULL, process, NULL);
sleep(5); /* Vlßkno se zatφm rozb∞hne */
if (pthread_cancel(thid) != 0)
printf("Cancel error\n");
pthread_join(thid[i], &thretval);
if (thretval != PTHREAD_CANCELED)
printf("Thread not be canceled.\n", i);
return 0;
}
|
V²pis Φ. 5: UkonΦenφ vlßkna
pthread_t pthread_self(void);
- vracφ identifikßtor vlßkna, kterΘ tuto funkci vyvolalo.
int pthread_equal(pthread_t thread1,
pthread_t thread2);
- porovnß, zda se identifikßtory vlßken rovnajφ.
int pthread_detach(pthread_t th);
- odpojφ vlßkno. V╣echny pam∞╗ovΘ prost°edky, kterΘ vlßkno pou╛φvß,
budou po ukonΦenφ vlßkna okam╛it∞ uvoln∞ny. S odpojen²m vlßknem se nelze
synchronizovat a vyzvednout jeho nßvratov² k≤d funkcφ pthread_join(3).
int pthread_attr_init(pthread_attr_t *attr);
- inicializuje objekt atribut∙ na implicitnφ hodnoty. Tento
objekt se dß pou╛φt pro vytvo°enφ vφce vlßken.
int pthread_attr_destroy(pthread_attr_t *attr);
- uvolnφ v╣echny prost°edky pot°ebnΘ pro objekt atribut∙.
- knihovna pro vlßkna pou╛φvß signßly SIGUSR1 a SIGUSR2, proto je program pou╛φvat nem∙╛e.
- pokud budete vlßkna pou╛φvat v X aplikacφch, je t°eba mφt Xlib
kompilovßnu s -D_REENTRANT a podporou vlßken (knihovna musφ b²t
napsßna reentrantn∞ - vφce vlßken m∙╛e vykonßvat tutΘ╛ funkci ve
stejnou chvφli, bez vzßjemnΘho ovlivn∞nφ - funkce nepou╛φvajφ
globßlnφ prom∞nnΘ). TotΘ╛ platφ o jakΘkoliv knihovn∞, kterou budete
v programu pou╛φvat. Pokud knihovna takto reentrantnφ nenφ, je mo╛no
ji pou╛φvat, ale pouze z hlavnφho vlßkna (k≤du procesu). Toto souvisφ
s prom∞nnou errno. Ka╛dΘ vlßkno mß toti╛ vlastnφ, pouze hlavnφ
vlßkno pou╛φvß globßlnφ errno.
- pou╛φvßnφ vlßken v C++ s libg++ asi nebude fungovat. Pro pou╛φvßnφ
vlßken v C++ je doporuΦen p°ekladaΦ egcs a knihovna libstdc++.
- pokud program vytvo°φ nap°φklad 2 vlßkna, nedivte se, ╛e vidφte 4
stejnΘ procesy. Jeden je hlavnφ proces, pak vidφte 2 vlßkna a poslednφ je
vlßkno starajφcφ se o sprßvn² chod vlßken. Toto vlßkno je vytvo°eno
knihovnou pthread.
Na adrese http://www.serpentine.com/~bos/threads-faq/
lze najφt Φasto kladenΘ otßzky newsovΘ skupiny
comp.programming.threads.
Bli╛╣φ informace o linuxov²ch vlßknech naleznete na adrese
http://pauillac.inria.fr/~xleroy/linuxthreads.
Lze zde takΘ najφt tutorial.
Na adrese http://www.rdg.opengroup.org/onlinepubs/7908799/index.html
najdete X/Open Group Single Unix specification, kde by se m∞l takΘ dßt
najφt bli╛╣φ popis aplikaΦnφho rozhranφ pro vlßkna.
Na adrese http://www.cs.wustl.edu/~schmidt/ACE.html
najdete projekt, kter² takΘ usnad≥uje pou╛φvßnφ vlßken v C++.
|