Linux - Vlákna (Threads)
Vykonávání programu může být rozděleno do více "proudů", vláken, které běží paralelně.
Vlákna tedy, stejně jako procesy, umožňují aplikaci dělat více
věcí najednou.
Vlákno existuje uvnitř procesu. Při spuštění programu se vytvoří proces a
v něm jedno vlákno, které může spustit další. Všechna tato vlákna běží
paralelně ve
stejném procesu, ale mohou vykonávat různé části (kód) programu.
Dvě různá vlákna téhož procesu sdílejí stejnou paměť, deskriptory souborů a
jiné systémové zdroje přidělené celému procesu. Protože proces a všechna jeho vlákna vykonávají jeden
program, když jedno vlákno zavolá exec
, všechna ostatní
vlákna jsou
ukončena.
GNU/Linux implementuje API vláken podle standardu POSIX (pthreads).
Všechny funkce a datové typy jsou deklarovány v
<pthread.h>
. Funkce nejsou ve
standardní C knihovně, ale v knihovně libpthread
, takže
je potřeba linkovat
s parametrem -lpthread.
Vytvoření vlákna
#include <bits/pthreadtypes.h> |
typedef unsigned long int pthread_t; |
pthread_self(3) |
#include <pthread.h> |
pthread_t pthread_self(void); |
|
Př.
Napište program, který vypíše ID svého hlavního vlákna.
Program nemažte, budete ho rozšiřovat o další probírané funkce!
pthread_equal(3) |
#include <pthread.h> |
int pthread_equal(pthread_t thread1, pthread_t thread2); |
-
vrací nenulové číslo, když thread1 a thread2 jsou id téhož vlákna,
jinak 0
|
Po spuštění každé vlákno vykonává funkci vlákna. Když tato funkce skončí,
skončí i vlákno. Má jediný parametr void*
, vrací také
void*
.
pthread_create(3) |
#include <pthread.h> |
int pthread_create(pthread_t * thread, pthread_attr_t * attr,
void * (*start_routine)(void *), void * arg);
|
-
vytvoří nové vlákno, které vykoná funkci start_routine s parametrem
arg
-
attr specifikuje atributy vlákna (viz
pthread_attr_init ),
pokud je NULL,
jsou defaultní: joinable, defaultní priorita
-
vrací 0 a id vlákna na místo, kam ukazuje argument thread, při chybě
nenulové číslo
- funkce se vrací ihned, obě vlákna pak pokračují asynchronně
|
Př.
Vytvořte vlákno s defaultními atributy a bez parametrů funkce vlákna.
Při vytváření atributů vlákna se postupuje takto:
- vytvoří se struktura
pthread_attr_t
-
pomocí
pthread_attr_init
se inicializuje na defaultní hodnoty
- změní se hodnoty ve struktuře
- zavolá se
pthread_create
a předá se jí tato struktura
-
pthread_attr_destroy
strukturu zruší, ale nedealokuje
(pokud je vytvořena
dynamicky)
S jednou strukturou je možné vytvořit více vláken a je možné ji zrušit po
vytvoření vlákna.
pthread_attr_init(3) |
#include <pthread.h> |
int pthread_attr_init(pthread_attr_t *attr); |
- inicializuje strukturu atributů defaultními hodnotami
- vrací 0, při chybě nenulové číslo
|
pthread_attr_destroy(3) |
#include <pthread.h> |
int pthread_attr_destroy(pthread_attr_t *attr); |
-
zruší strukturu atributů, před dalším použitím se musí znova
inicializovat
- vrací 0, při chybě nenulové číslo
- v Linuxu nedělá nic
|
pthread_attr_setdetachstate(3) pthread_attr_getdetachstate(3)
|
#include <pthread.h> |
int pthread_attr_setdetachstate(pthread_attr_t *attr, int
detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr,
int *detachstate);
|
- nastaví(vrací) atribut z(do) druhého parametru
- vrací 0, při chybě nenulové číslo
- atribut detachstate může být:
- PTHREAD_CREATE_JOINABLE - vlákno bude joinable, defaultní
- PTHREAD_CREATE_DETACHED - detached
|
Joinable vlákno není po skončení automaticky odstraněno ze
systému, jeho
status ukončení je udržován, dokud si ho nevyzvedne jiné vlákno pomocí
pthread_join
(stejné jako zombie u procesu).
Detached vlákno je naopak hned
po skončení automaticky odstraněno a nelze získat jeho informace o ukončení
pomocí pthread_join
.
Př.
Změňte vytvářené vlákno na detached.
Typ argumentu funkce vlákna je void*
, nelze tedy poslat
data (>4B) přímo.
Předává se ukazatel na strukturu dat. Tato struktura ale nesmí být lokální
v jednom vláknu (př. hlavním), protože by mohlo skončit dříve než ostatní,
která tuto strukturu používají. Pokud je lokální, možným řešením je ve vláknu počkat, až ostatní
vlákna skončí. Struktura se nesmí dealokovat dřív, než vlákna, která ji
používají, skončí.
Př.
Předejte funkci vlákna 2 řetězce jako parametry, ty spojte a výsledek vraťte
jako návratovou hodnotu funkce vlákna.
pthread_join(3) |
#include <pthread.h> |
int pthread_join(pthread_t th, void **thread_return); |
- pozastaví vlákno, dokud neskončí vlákno specifikované pomocí th
-
pokud thread_return není NULL, vrátí návratovou hodnotu funkce vlákna na
místo, kam ukazuje, pokud je vlákno zrušeno, je tam PTHREAD_CANCELED
- jen jedno vlákno může čekat na skončení jiného vlákna
- vrací 0, při chybě nenulové číslo
- vlákno nesmí čekat samo na sebe, to je deadlock!
|
Př.
Počkejte na dokončení vlákna a vypište výsledek spojení.
pthread_detach(3) |
#include <pthread.h> |
int pthread_detach(pthread_t th); |
-
"přepne" vlákno na detached, pokud jiné vlákno nečeká na jeho ukončení
- vrací 0, při chybě nenulové číslo
|
Ukončení vlákna
pthread_exit(3) |
#include <pthread.h> |
void pthread_exit(void *retval); |
- ukončí vlákno s návratovým kódem retval
- nikdy se neukončuje
|
pthread_cancel(3) |
#include <pthread.h> |
int pthread_cancel(pthread_t thread); |
-
pošle vláknu, specifikovanému pomocí thread, požadavek na ukončení
- vrací 0, při chybě nenulové číslo
|
Vlákno často vykonává kód, který nesmí být přerušen. To je zajištěno tím, že
vlákno samo si může určit místa, kde může být zrušeno a kde ne, pomocí svého
stavu:
-
asynchronně zrušitelné - může být zrušeno kdekoliv
-
synchronně zrušitelné - může být zrušeno až v určitých místech (body
zrušení), požadavek je zaznamenán, defaultní při vytvoření vlákna
-
nezrušitelné - požadavky na zrušení jsou ignorovány
pthread_setcanceltype(3) |
#include <pthread.h> |
int pthread_setcanceltype(int type, int *oldtype); |
- změní typ reakce na zrušení vlákna na type
- type může být:
- PTHREAD_CANCEL_ASYNCHRONOUS - asynchronní
- PTHREAD_CANCEL_DEFERRED - synchronní
-
pokud oldtype není NULL, na místo, kam ukazuje, je uložen předchozí
typ
- vrací 0, při chybě nenulové číslo
|
pthread_testcancel(3) |
#include <pthread.h> |
void pthread_testcancel(void); |
- vytvoří bod zrušení
- existují implicitní body zrušení (viz man), př.
pthread_join
|
Př.
Změňte vlákno na synchronně zrušitelné a vytvořte body zrušení před a
po spojení.
pthread_setcancelstate(3) |
#include <pthread.h> |
int pthread_setcancelstate(int state, int *oldstate); |
- změní stav zrušení vlákna na state
- state může být:
- PTHREAD_CANCEL_ENABLE - zrušitelný stav
- PTHREAD_CANCEL_DISABLE - nezrušitelný
-
pokud oldstate není NULL, na místo, kam ukazuje, je uložen předchozí
stav
- vrací 0, při chybě nenulové číslo
- umožňuje vymezení kritických sekcí - nepřerušitelný kód
|
Př.
Uzavřete spojení do kritické sekce.
PŘÍKLAD
Lokální uložení dat
Vlákna sdílejí stejný adresový prostor. Někdy je ale potřeba, aby každé
vlákno mělo vlastní kopii určité globální proměnné. Každé vlákno poskytuje
lokální uložení dat (thread-specific data - TSD - area),
kde se ukládají kopie
globálních proměnných specifické pro každé vlákno. Proměnné v tomto uložení
jsou indexovány přes klíče. Klíče jsou společné všem vláknům, hodnota proměnné
je v každém vláknu jiná.
pthread_key_create(3) |
#include <pthread.h> |
int pthread_key_create(pthread_key_t *key,
void (*destr_function) (void *));
|
-
vytvoří nový TSD klíč a uloží ho, kam ukazuje key, asociovaná hodnota
je NULL
-
pokud destr_function není NULL, je to funkce, která se zavolá po
ukončení vlákna s hodnotou asociované proměnné jako parametrem, před
jejím voláním je ke klíči asociován NULL
- vrací 0, při chybě nenulovou hodnotu
|
pthread_key_delete(3) |
#include <pthread.h> |
int pthread_key_delete(pthread_key_t key); |
- zruší TSD klíč
- vrací 0, při chybě nenulovou hodnotu
|
pthread_setspecific(3) |
#include <pthread.h> |
int pthread_setspecific(pthread_key_t key, const void *pointer);
|
- změní hodnotu proměnné asociované s klíčem key na pointer
- vrací 0, při chybě nenulovou hodnotu
|
pthread_getspecific(3) |
#include <pthread.h> |
void * pthread_getspecific(pthread_key_t key); |
- vrací hodnotu proměnné asociované s klíčem key, při chybě NULL
|
PŘÍKLAD
OTÁZKA: Procesy versus vlákna? Najděte co nejvíc rozdílů.
Další funkce týkající se vláken jsou:
-
pthread_cleanup_push(3) /
pthread_cleanup_pop(3)
-
pthread_setschedparam(3) /
pthread_getschedparam(3)
- pthread_kill(3)
- pthread_sigmask(3)
- sigwait(3)