Sunteți pe pagina 1din 18

Thread-uri

Contents
1 Prezentare teoretic 1.1 Introducere 1.1.1 Diferente dintre thread-uri i procese 1.2 Avantajele thread-urilor 1.3 Tipuri de thread-uri 1.3.1 Kernel Level Threads 1.3.2 User Level Threads 1.3.3 Fire de execuie hibride 2 Funcii n Linux 2.1 Suport POSIX 2.2 Crearea firelor de execuie 2.3 Ateptarea firelor de execuie 2.4 Terminarea firelor de execuie 2.5 Thread Specific Data 2.5.1 Crearea i tergerea unei variabile 2.5.2 Modificarea i citirea unei variabile 2.6 Funcii pentru cleanup 2.7 Atributele unui thread 2.8 Cedarea procesorului 2.9 Alte operaii 2.10 Compilare 2.11 Exemplu 3 Funcii n Windows 3.1 Crearea firelor de execuie 3.2 Handle i identificator 3.3 Ateptarea firelor de execuie 3.4 Terminarea firelor de execuie 3.5 Suspend, Resume 3.6 Cedarea procesorului 3.7 Alte funcii utile 3.8 Thread Local Storage 3.9 Fibre de execuie 3.10 Securitate i drepturi de acces 3.11 Exemplu 4 Quiz 5 Exerciii 5.1 Warning 5.2 Prezentare 5.3 Linux 5.4 Windows 6 Soluii

Thread-uri

Prezentare teoretic
Introducere
n laboratoarele anterioare a fost prezentat conceptul de proces, acesta fiind unitatea elementar de alocare a resurselor utilizatorilor. n acest laborator este prezentat conceptul de fir de execuie (sau thread), acesta fiind unitatea elementar de planificare ntr-un sistem. Ca i procesele, thread-urile reprezint un mecanism prin care un calculator poate sa ruleze mai multe lucruri simultan. Un fir de execuie exist n cadrul unui proces, i reprezint o unitate de execuie mai fin dect acesta. n momentul n care un proces este creat, n cadrul lui exist un singur fir de execuie, care execut programul secvenial. Acest fir poate la rndul lui sa creeze alte fire de execuie; aceste fire vor rula poriuni ale binarului asociat cu procesul curent, posibil aceleai cu firul iniial (care le-a creat).

Diferente dintre thread-uri i procese procesele nu partajeaz resurse ntre ele (dect dac programatorul folosete un mecanism special pentru asta - vezi IPC), pe cnd thread-urile partajeaz n mod implicit majoritatea resurselor unui proces. Modificarea unei astfel de resurse dintr-un fir este vizibil instantaneu i celorlalte: segmentele de memorie precum .heap, .data i .bss (deci i variabilele stocate n ele) descriptorii de fiiere (aadar, nchiderea unui fiier este vizibil imediat pentru toate thread-urile) sockeii fiecare fir are un context de execuie propriu, format din stiv set de regitri (deci i un contor de program - registrul (E)IP) Procesele sunt folosite de SO pentru a grupa i aloca resurse, iar firele de execuie pentru a planifica execuia de cod care acceseaz (n mod partajat) aceste resurse.

Avantajele thread-urilor
Deoarece thread-urile aceluiai proces folosesc toate spaiul de adrese al procesului de care aparin, folosirea lor are o serie de avantaje: crearea/distrugerea unui thread dureaz mai puin dect crearea/distrugerea unui proces timpul context switch-ului ntre thread-urile aceluiai proces este foarte mic, ntruct nu e necesar s se "comute" i spaiul de adrese (pentru mai multe informaii, cutai "TLB flush" pe google) comunicarea ntre thread-uri are un overhead minim (practic se face prin modificarea unor zone de memorie din spaiul de adres) Firele de execuie se pot dovedi utile n multe situaii, de exemplu, pentru a mbunti timpul de rspuns al aplicaiilor cu interfee grafice (GUI), unde prelucrrile CPU-intensive se fac de obicei ntr-un thread diferit de cel care afieaz interfaa. De asemenea, ele simplific structura unui program i conduc la utilizarea unui numr mai mic de resurse (pentru c nu mai este nevoie de diversele forme de IPC pentru a comunica). Prezentare teoretic 2

Tipuri de thread-uri
Exist 3 categorii de thread-uri : Kernel Level Threads (KLT) User Level Threads (ULT) Fire de execuie hibride

Kernel Level Threads Managementul thread-urilor este fcut de kernel, i programele user-space pot crea/distruge thread-uri printr-un set de apeluri de sistem. Kernel-ul menine informaii de context att pentru procese ct i pentru thread-urile din cadrul proceselor, iar planificarea pentru execuie se face la nivel de thread. Avantaje : dac avem mai multe procesoare putem lansa n execuie simultan mai multe thread-uri ale aceluiai proces; blocarea unui fir nu nseamn blocarea ntregului proces. putem scrie cod n kernel care s se bazeze pe thread-uri. Dezavantaje : comutarea de context o face kernelul, deci pentru fiecare schimbare de context se trece din firul de execuie n kernel i apoi se mai face nc o schimbare din kernel n alt fir de execuie, deci viteza de comutare este mic.

User Level Threads Kernel-ul nu este contient de existena lor, i managementul lor este fcut de procesul n care ele exist, folosind de obicei o bibliotec. Astfel, schimbarea contextului nu necesit intervenia kernel-ului, iar algoritmul de planificare depinde de aplicaie. Avantaje : schimbarea de context nu implic kernelul, deci avem o comutare rapid planificarea poate fi aleas de aplicaie si deci se poate alege una care s favorizeze creterea vitezei aplicaiei noastre thread-urile pot rula pe orice SO, deci i pe cele care nu suport thread-uri (au nevoie doar de biblioteca ce le implementeaz) Dezavantaje : kernel-ul nu tie de thread-uri, deci dac un thread apeleaz ceva blocant toate thread-urile planificate de aplicaie vor fi blocate. Cele mai multe apeluri de sistem sunt blocante kernel-ul planific thread-urile de care tie, fiecare pe un singur procesor la un moment dat. n cazul user-level threads, el va vedea un singur thread. Astfel, chiar dac 2 thread-uri user-level sunt implementate folosind un singur thread "vzut" de kernel, ele nu vor putea folosi eficient resursele sistemului (vor mpri amndou un acelai procesor). Tipuri de thread-uri 3

Fire de execuie hibride Aceste fire ncearc s combine avantajele thread-urilor user-level cu cele ale thread-urilor kernel-level. O modalitate de a face acest lucru este de a utiliza fire kernel-level pe care s fie multiplexate fire user-level. KLT sunt unitile elementare care pot fi distribuite pe procesoare. De regul crearea thread-urilor se face n user space i tot aici se face aproape toat planificarea i sincronizarea. Kernel-ul tie doar de KLT-urile pe care sunt multiplexate ULT, i doar pe acestea le planific. Programatorul poate schimba eventual numrul de KLT alocate unui proces.

Funcii n Linux
Suport POSIX
n ceea ce privete thread-urile, POSIX nu specific dac acestea trebuie implementate n user-space sau kernel-space. Linux le implementeaz n kernel-space, dar nu difereniaz thread-urile de procese dect prin faptul c thread-urile partajeaz spaiul de adres (att thread-urile, ct i procesele, sunt un caz particular de "task"). Pentru folosirea thread-urilor n Linux trebuie s includem header-ul pthread.h unde se gsesc declaraiile funciilor i tipurilor de date necesare i s utilizm biblioteca libpthread.

Crearea firelor de execuie


Pentru crearea unui nou fir de execuie se foloseste funcia pthread_create :
#include <pthread.h> int pthread_create(pthread_t *tid, const pthread_attr_t *tattr, void*(*start_routine)(void *), void *arg);

Noul fir creat se va executa concurent cu firul de execuie din care a fost creat. Acesta va executa codul specificat de funcia start_routine creia i se va pasa argumentul arg. Folosind arg se poate transmite firului de execuie un pointer la o structur care sa conin toi "parametrii" necesari acestuia. Prin parametrul tattr se stabilesc atributele noului fir de execuie. Dac transmitem valoarea NULL thread-ul va fi creat cu atributele implicite

Ateptarea firelor de execuie


La fel ca la procese, un printe i poate atepta fiul apelnd pthread_join (nlocuiete waitpid).
int pthread_join(pthread_t th, void **thread_return);

Primul parametru specific identificatorul firului de execuie ateptat, iar al doilea parametru specific unde se va plasa codul ntors de funcia copil (printr-un pthread_exit sau printr-un return). n caz de succes se ntoarce valoarea 0, altfel se ntoarce o valoare negativ reprezentnd un cod de eroare.

Fire de execuie hibride

Thread-urile se mpart n dou categorii : unificabile : permit unificarea cu alte threaduri care apeleaz pthread_join. resursele ocupate de thread nu sunt eliberate imediat dup terminarea threadului, ci mai sunt pstrate pn cnd un alt thread va executa pthread_join (analog cu procesele zombie) threadurile sunt implicit unificabile detaabile un thread este detaabil dac : a fost creat detaabil. i s-a schimbat acest atribut n timpul execuiei prin apelul pthread_detach. nu se poate executa un pthread_join pe ele vor elibera resursele imediat ce se vor termina (analog cu ignorarea semnalului SIGCHLD n printe atunci cnd se termin procesele copil)

Terminarea firelor de execuie


Un fir de execuie se termin la un apel al funciei pthread_exit :
void pthread_exit(void *retval);

Dac nu exist un astfel de apel este adugat unul, n mod automat, la sfritul codului firului de execuie. Prin parametrul retval se comunic printelui un mesaj despre modul de terminare al copilului. Aceast valoare va fi preluat de funcia pthread_join. Metodele ca un fir de execuie s termine un alt thread sunt: stabilirea unui protocol de terminare (spre exemplu, firul master seteaz o variabil global, pe care firul slave o verific periodic). mecanismul de "thread cancellation", pus la dispozitie de libpthread. Totui, aceast metod nu este recomandat, pentru c este greoaie, i pune probleme foarte delicate la clean-up. Pentru mai multe detalii, consultai urmatorul material scris de echipa SO: Terminarea thread-urilor

Thread Specific Data


Uneori este util ca o variabil s fie specific unui thread (invizibil pentru celelalte thread-uri). Linux permite memorarea de perechi (cheie, valoare) ntr-o zon special desemnat din stiva fiecrui thread al procesului curent. Cheia are acelai rol pe care o are numele unei variabile: desemneaz locaia de memorie la care se afl valoarea. Fiecare thread va avea propria copie a unei "variabile" corespunztoare unei chei k, pe care o poate modifica, fr ca acest lucru s fie observat de celelalte thread-uri, sau s necesite sincronizare. De aceea, TSD este folosit uneori pentru a optimiza operaiile care necesit mult sincronizare ntre thread-uri: fiecare thread calculeaz informaia specific, i exist un singur pas de sincronizare la sfrit, necesar pentru reunirea rezultatelor tuturor thread-urilor. Ateptarea firelor de execuie 5

Cheile sunt de tipul pthread_key_t, iar valorile asociate cu ele, de tipul generic void* (pointeri ctre locaia de pe stiv unde este memorat variabila respectiv). Descriem n continuare operaiile disponibile cu variabilele din TSD:

Crearea i tergerea unei variabile O variabil se creaz folosind:


int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *));

Al doilea parametru reprezint o funcie de cleanup. Acesta poate avea una din valorile: NULL, i este ignorat pointer ctre o funcie de clean-up care se execut la terminarea thread-ului Pentru tergerea unei variabile se apeleaz:
int pthread_key_delete(pthread_key_t key);

Ea nu apeleaz funcia de cleanup asociat acesteia.

Modificarea i citirea unei variabile Dup crearea cheii, fiecare fir de execuie poate modifica propria copie a variabilei asociate folosind funcia pthread_setspecific :
int pthread_setspecific(pthread_key_t key, const void *pointer);

Primul parametru reprezint cheia, iar al doilea parametru reprezint valoarea specific ce trebuie stocat i care este de tipul void*. Pentru a determina valoarea unei variabile de tip TSD se folosete funcia :
void* pthread_getspecific(pthread_key_t key);

Funcii pentru cleanup


Funciile de cleanup asociate TSD-urilor pot fi foarte utile pentru a asigura faptul c resursele sunt eliberate atunci cnd un fir se termin singur sau este terminat de ctre un alt fir. Uneori poate fi util s se poat specifica astfel de funcii fr a crea neaprat un thread specific data. Pentru acest scop exista funciile de cleanup. O astfel de funcie de cleanup este o funcie care este apelat cnd un thread se termin. Ea primete un singur parametru de tipul void * care este specificat la nregistrarea funciei. O funcie de cleanup este folosit pentru a elibera o resurs numai n cazul n care un fir de execuie apeleaz pthread_exit sau este terminat de un alt fir folosind pthread_cancel. n circumstane normale, atunci cnd un fir nu se termin n mod forat, resursa trebuie eliberat explicit, iar funcia de cleanup trebuie Thread Specific Data 6

sa fie scoas. Pentru a nregistra o astfel de funcie de cleanup se folosete :


void pthread_cleanup_push(void (*routine) (void *), void *arg);

Aceasta funcie primete ca parametri un pointer la funcia care este nregistrat i valoarea argumentului care va fi transmis acesteia. Funcia routine va fi apelat cu argumentul arg atunci cnd firul este terminat forat. Daca sunt nregistrate mai multe funcii de cleanup, ele vor fi apelate n ordine LIFO (cea mai recent instalat va fi prima apelat). Pentru fiecare apel pthread_cleanup_push trebuie s existe i apelul corespunztor pthread_cleanup_pop care denregistreaz o funcie de cleanup :
void pthread_cleanup_pop(int execute);

Aceast funcie va denregistra cea mai recent instalat funcie de cleanup, i dac parametrul execute este nenul o va i executa. Atentie! Un apel pthread_cleanup_push trebuie s aib un apel corespunztor pthread_cleanup_pop n aceeai funcie i la acelai nivel de imbricare. Un mic exemplu de folosire a funciilor de cleanup :
void *alocare_buffer(int size) { return malloc(size); } void dealocare_buffer(void *buffer) { free(buffer); } /* functia apelata de un thread */ void functie() { void *buffer = alocare_buffer(512); /* inregistrarea functiei de cleanup */ pthread_cleanup_push(dealocare_buffer, buffer);

/* aici au loc prelucrari, si se poate apela pthread_exit sau firul poate fi terminat de un alt f /* deinregistrarea functiei de cleanup si executia ei (parametrul dat este nenul) */ pthread_cleanup_pop(1); }

Atributele unui thread


Atributele reprezint o modalitate de specificare a unui comportament diferit de comportamentul implicit. Atunci cnd un fir de execuie este creat cu pthread_create se poate specifica un atribut pentru respectivul fir de execuie. Atributele implicite sunt suficiente pentru marea majoritate a aplicaiilor. Cu Funcii pentru cleanup 7

ajutorul unui atribut se pot schimba: starea: unificabil sau detaabil politica de alocare a procesorului pentru thread-ul respectiv (round robin, FIFO, sau system default) prioritatea (cele cu prioritate mai mare vor fi planificate, n medie, mai des) dimensiunea i adresa de start a stivei Mai multe detalii putei gsi la seciunea suplimentar dedicat.

Cedarea procesorului
Un thread cedeaz dreptul de executie unui alt thread, n urma unuia din urmtoarele evenimente: efectueaz un apel blocant (cerere de I/O, sincronizare cu un alt thread) i kernel-ul decide c este rentabil s faca un context switch i-a expirat cuanta de timp alocat de ctre kernel cedeaz voluntar dreptul, folosind funcia:
#include <sched.h> int sched_yield(void);

Dac exist alte procese interesate de procesor acesta li se ofer, iar dac nu exist nici un alt proces n ateptare pentru procesor, firul curent i continu execuia.

Alte operaii
Dac dorim s fim siguri c un cod de iniializare se execut o singur dat putem folosi funcia :
pthread_once_t once_control = PTHREAD_ONCE_INIT; int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));

Scopul funciei pthread_once este de a asigura c o bucat de cod (de obicei folosit pentru iniializri) se execute o singur dat. Argumentul once_control este un pointer la o variabil iniializat cu PTHREAD_ONCE_INIT. Prima oar cnd aceast funcie este apelat ea va apela funcia init_routine i va schimba valoarea variabilei once_control pentru a ine minte c iniializarea a avut loc. Urmtoarele apeluri ale acestei funcii cu acelai once_control nu vor face nimic. Funcia pthread_once ntoarce ntotdeauna 0. Pentru a determina identificatorul thread-ului curent se poate folosi funcia :
pthread_t pthread_self(void);

Pentru a determina dac doi identificatori se refer la acelai thread se poate folosi :
int pthread_equal(pthread_t thread1, pthread_t thread2);

Pentru aflarea/modificarea prioritilor sunt disponibile urmtoarele apeluri :


int pthread_setschedparam(pthread_t target_thread, int policy, const struct sched_param *param);

Atributele unui thread

int pthread_getschedparam(pthread_t target_thread, int *policy, struct sched_param *param);

Compilare
La compilare trebuie specificat i biblioteca libpthread (deci se va folosi argumentul -lpthread). Atentie! Nu link-ai un program single-threaded cu aceast bibliotec. Daca facei aa ceva se vor stabili nite mecanisme multithreading care vor fi iniializate la execuie. Atunci programul va fi mult mai lent, va ocupa mult mai multe resurse i va fi mult mai dificil de debug-at.

Exemplu
n continuare este prezentat un exemplu simplu n care sunt create 2 fire de execuie, fiecare afind un caracter de un anumit numr de ori pe ecran.
#include <pthread.h>; #include <stdio.h>; /* structura ce contine parametrii transmisi fiecarui thread */ struct parametri { char caracter; /* caracterul afisat */ int numar; /* de cate ori va fi afisat */ }; /* functia executata de thread-uri */ void* afisare_caracter(void *params) { struct parametri* p = (struct parametri*) params; int i; for (i=0;i<p->numar;i++) printf("%c", p->caracter); printf("\n"); return NULL; } int main() { pthread_t fir1, fir2; struct parametri fir1_args, fir2_args; /* cream un thread care va afisa 'x' de 11 ori */ fir1_args. caracter = 'x'; fir1_args. numar = 11; if (pthread_create(&fir1, NULL, &afisare_caracter, &fir1_args)) { perror("pthread_create"); exit(1); } /* cream un thread care va afisa 'y' de 13 ori */ fir2_args. caracter = 'y'; fir2_args. numar = 13; if (pthread_create(&fir2, NULL, &afisare_caracter, &fir2_args)) { perror("pthread_create"); exit(1);

Alte operaii

} /* asteptam terminarea celor doua fire de executie */ if (pthread_join(fir1, NULL)) perror("pthread_join"); if (pthread_join(fir2, NULL)) perror("pthread_join"); return 0; }

Comanda utilizat pentru a compila acest exemplu va fi:


gcc -o exemplu exemplu.c -lpthread

Funcii n Windows
Crearea firelor de execuie
Pentru a lansa un nou fir de execuie exist funciile CreateThread i CreateRemoteThread (a doua fiind folosit pentru a crea un fir de execuie n cadrul altui proces dect cel curent).
HANDLE CreateThread ( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );

dwStackSize reprezint mrimea iniial a stivei, n bytes. Sistemul rotunjete aceast valoare la cel mai apropiat multiplu de dimensiunea unei pagini. Dac parametrul este 0, noul thread va folosi mrimea implicit. lpStartAddress este un pointer la funcia ce trebuie executat de ctre thread. Aceast funcie are urmtorul prototip:
DWORD WINAPI ThreadProc( LPVOID lpParameter );

unde lpParameter reprezint datele care sunt pasate firului de execuie la execuie. La fel ca pe Linux, se poate transmite un pointer la o structur, care conine toi parametrii necesari. Rezultatul ntors poate fi obinut de un alt thread folosind funcia GetExitCodeThread. Un mic exemplu :
HANDLE hthread; hthread = CreateThread(NULL, 0, ThreadFunc, &dwThreadParam, 0, &dwThreadId);

La crearea unui nou fir de execuie parametrii cei mai importani sunt funcia pe care acesta o va executa i parametrul care este pasat acesteia.

Exemplu

10

Handle i identificator
Thread-urile pot fi identificate n sistem n 3 moduri: printr-un HANDLE, obinut la crearea thread-ului, sau folosind funcia OpenThread, creia i se d ca parametru identificatorul thread-ului:
HANDLE OpenThread( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId );

printr-un pseudo-HANDLE, o valoare special care indic funciilor de lucru cu HANDLE-uri c este vorba de HANDLE-ul asociat cu thread-ul curent (obinut, de exemplu, apelnd GetCurrentThread). Pentru a converti un pseudo-HANDLE ntr-un HANDLE veritabil, trebuie folosit funcia DuplicateHandle. De asemenea, nu are sens s facem CloseHandle pe un pseudo-HANDLE. Pe de alt parte, handle-ul obinut cu DuplicateHandle trebuie inchis daca nu mai este nevoie de el. printr-un identificator de thread, de tipul DWORD, ntors la crearea thread-ului, sau obinut folosind GetCurrentThreadId. O diferen dintre identificator i HANDLE este faptul c nu trebuie s ne preocupm sa nchidem un identificator, pe cnd la HANDLE, pentru a evita leak-urile, trebuie s apelm CloseHandle Handle-ul obinut la crearea unui thread are implicit drepturi de acces nelimitate. El poate fi motenit sau nu de procesele copil ale procesului curent n funcie de flag-urile specificate la crearea lui. Prin funcia DuplicateHandle, se poate crea un nou handle cu mai puine drepturi. Handle-ul este valid pn ce este nchis, chiar dac firul de execuie pe care l reprezint s-a terminat.

Ateptarea firelor de execuie


Pe Windows, se poate atepta terminarea unui fir de execuie folosind aceeai funcie ca pentru ateptarea oricrui obiect de sincronizare:
DWORD WINAPI WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );

Terminarea firelor de execuie


Un fir de execuie se termin n unul din urmtoarele cazuri : el nsui apeleaz funcia ExitThread :
void ExitThread(DWORD dwExitCode);

funcia asociat firului de execuie execut un return. un fir de execuie ce deine un handle cu dreptul THREAD_TERMINATE asupra firului de execuie, execut un apel TerminateThread pe acest handle : Handle i identificator 11

BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);

unde dwExitCode specific codul de terminare al threadului. sau ntregul proces se termin ca urmare a unui apel ExitProcess sau TerminateProcess. Pentru aflarea codului de terminare a unui fir de execuie folosim funcia GetExitCodeThread, i acest cod poate fi: STILL_ACTIVE daca firul de execuie nu s-a terminat. valoarea ntoars de funcia asociat firului de execuie. valoarea specificat la apelul uneia din funciile TerminateThread, TerminateProcess, ExitThread sau ExitProcess. Atenie! Funciile TerminateThread i TerminateProcess nu trebuie folosite dect n cazuri extreme (pentru c nu elibereaz resursele folosite de firul de execuie, iar unele resurse pot fi VITALE). Metoda preferat de a termina un fir de execuie este ExitThread, sau folosirea unui protocol de oprire ntre thread-ul care dorete s nchid un alt thread i thread-ul care trebuie oprit. La terminarea ultimului fir de execuie al unui proces se termin i procesul.
BOOL GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode);

hThread - handle la firul de execuie n discuie ce trebuie s aib dreptul de acces THREAD_QUERY_INFORMATION. lpExitCode - pointer la o variabil n care va fi plasat codul de terminare al firului. Dac firul nu i-a terminat execuia, aceast valoare va fi STILL_ACTIVE. Atentie! Pot aprea probleme dac firul de execuie returneaz chiar STILL_ACTIVE (259), i anume aplicaia care testeaz valoarea poate intra ntr-o bucl infinit. Dac funcia se termin cu succes va ntoarce o valoare nenul. Altfel ntoarce 0, iar eroarea poate fi aflat folosind GetLastError.

Suspend, Resume
DWORD SuspendThread(HANDLE hThread); DWORD ResumeThread(HANDLE hThread);

Prin intermediul acestor dou funcii un fir de execuie poate suspenda/relua execuia unui alt fir de execuie. Un fir de execuie suspendat nu mai este planificat pentru a obine timp pe procesor. Cele doua funcii manipuleaz un contor de suspendare (prin incrementare, respectiv decrementare - n limitele 0 - MAXIMUM_SUSPEND_COUNT). n cazul n care contorul de suspendare este mai mare strict dect 0, firul de execuie este suspendat. Un fir de execuie poate fi creat n starea suspendat folosind flag-ul CREATE_SUSPENDED.

Terminarea firelor de execuie

12

Aceste funcii nu pot fi folosite pentru sincronizare (pentru ca nu controleaz punctul n care firul de execuie i va suspenda execuia), dar sunt utile pentru programe de debug.

Cedarea procesorului
Un fir de execuie poate renuna de bun voie la procesor. n urma apelului funciei Sleep un fir de execuie este suspendat pentru cel puin o anumit perioad de timp (dwMilliseconds).
void Sleep(DWORD dwMilliseconds);

Exist de asemenea funcia SleepEx care este un Sleep alertabil (ceea ce nseamn c se pot prelucra APC-uri - Asynchronous Procedure Call - pe durata execuiei lui SleepEx). Funcia SwitchToThread este asemntoare cu Sleep doar c nu este specificat intervalul de timp, astfel firul de execuie renun doar la timpul pe care l avea pe procesor n momentul respectiv (time-slice).
BOOL SwitchToThread(void);

Funcia ntoarce TRUE dac procesorul este cedat unui alt thread i FALSE dac nu exist alte thread-uri gata de execuie.

Alte funcii utile


HANDLE GetCurrentThread(void);

Rezultatul este un pseudohandle pentru firul curent ce nu poate fi folosit dect de firul apelant. Acest handle are maximum de drepturi de acces asupra obiectului pe care l reprezint.
DWORD GetCurrentThreadId(void);

Rezultatul este identificatorul firului de execuie.


DWORD GetThreadId(HANDLE Thread);

Rezultatul este identificatorul firului ce corespunde handle-ului Thread.

Thread Local Storage


Ca i n Linux, i n Windows exist un mecanism prin care fiecare fir de execuie s aib anumite date specifice. Acest mecanism poart numele de thread local storage (TLS). n Windows, pentru a accesa datele din TLS se folosesc indecii asociai acestora (corespunztori cheilor din Linux). Pentru a crea un nou TLS se apeleaz funcia :
DWORD TlsAlloc(void);

Suspend, Resume

13

Funcia ntoarce n caz de succes indexul asociat TLS-ului, prin intermediul cruia fiecare fir de execuie va putea accesa datele specifice. n caz de eec funcia ntoarce valoarea TLS_OUT_OF_INDEXES. Pentru a stoca o nou valoare ntr-un TLS se folosete funcia :
BOOL TlsSetValue( DWORD dwTlsIndex, LPVOID lpTlsValue );

Un thread poate afla valoarea specific lui dintr-un TLS apelnd funcia :
LPVOID TlsGetValue( DWORD dwTlsIndex );

unde dwTlsIndex este indexul asociat TLS-ului, alocat cu TlsAlloc. n caz de succes funcia ntoarce valoarea stocat n TLS, iar n caz de eec ntoarce 0. Dac data stocat n TLS are valoarea 0 atunci valoarea ntoars este tot 0, dar GetLastError va ntoarce NO_ERROR. Deci trebuie verificat eroarea ntoars de GetLastError. Pentru a elibera un index asociat unui TLS se folosete funcia :
BOOL TlsFree( DWORD dwTlsIndex );

unde dwTlsIndex este indexul asociat TLS-ului. Dac firele de execuie au alocat memorie i au stocat n TLS un pointer la memoria alocat, aceast funcie nu va face dezalocarea memoriei. Memoria trebuie dezalocat de ctre fire nainte de apelul lui TlsFree.

Fibre de execuie
Windows pune la dispoziie i o implementare de user-space threads, numite fibre. Kernel-ul planific un singur KLT asociat cu un set de fibre, iar fibrele colaboreaz pentru a partaja timpul de procesor oferit acestuia. Dei viteza de execuie este mai bun (pentru context-switch, nu mai este necesar interaciunea cu kernel-ul), programele scrise folosind fibre pot deveni complexe. Mai multe informaii putei gsi la seciunea suplimentar dedicat.

Securitate i drepturi de acces


Modelul de securitate Windows NT ne permite s controlm accesul la obiectele de tip fir de execuie. Descriptorul de securitate pentru un fir de execuie se poate specifica la apelul uneia dintre funciile CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW, CreateThread sau CreateRemoteThread. Thread Local Storage 14

Dac n locul acestui descriptor este pasat valoarea NULL, firul de execuie va avea un descriptor implicit. Pentru a obine acest descriptor este folosit funcia GetSecurityInfo, iar pentru a-l schimba funcia SetSecurityInfo. Handle-ul ntors de funcia CreateThread are THREAD_ALL_ACCESS. La apelul GetCurrentThread, sistemul ntoarce un pseudohandle cu maximul de drepturi de acces pe care descriptorul de securitate al firului de execuie l permite apelantului. Drepturile de acces pentru un obiect fir de execuie includ drepturile de acces standard : DELETE, READ_CONTROL, SYNCHRONIZE, WRITE_DAC i WRITE_OWNER la care se adaug drepturi specifice, pe care le putei gsi pe MSDN.

Exemplu
Exemplul prezint crearea a 2 fire de execuie ce vor folosi un TLS.
#include <stdio.h> #include <windows.h> #define NUMAR_FIRE 2 DWORD dwTlsIndex; VOID ErrorExit (LPTSTR lpszMessage) { fprintf(stderr, "%s\n", lpszMessage); ExitProcess (1); } VOID FolosireTLS(VOID) { LPVOID lpvData; // obtin pointer-ul stocat in TLS pentru firul curent lpvData = TlsGetValue(dwTlsIndex); if ((lpvData == 0) && (GetLastError() != 0)) ("Eroare ErrorExit la TlsGetValue"); // folosire date stocate printf("thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData); Sleep(5000); } // functia executata de cele doua fire DWORD WINAPI ThreadFunc(LPVOID) { LPVOID lpvData; // intializare TLS pentru acest thread. lpvData = (LPVOID) LocalAlloc(LPTR, 256); if (! TlsSetValue(dwTlsIndex, lpvData))

Securitate i drepturi de acces

15

("Eroare ErrorExit la TlsSetValue"); printf("thread %d: lpvData=%lx\n", GetCurrentThreadId(), lpvData); FolosireTLS (); // eliberare memorie alocata dinamic lpvData = TlsGetValue(dwTlsIndex); if (lpvData != 0) ((HLOCAL) LocalFree lpvData); return 0; } DWORD main(VOID) { DWORD IDThread; HANDLE hThread[NUMAR_FIRE]; int i; // alocare index TLS if ((dwTlsIndex = TlsAlloc()) == -1) ("Eroare ErrorExit la TlsAlloc"); // creare thread-uri for (i=0;i<NUMAR_FIRE;i++) { [i] = CreateThread(NULL, hThread 0, (LPTHREAD_START_ROUTINE) ThreadFunc, NULL, 0, &IDThread);

// // // // //

// fara atribute de securitate utilizare dimensiune implicita pentru stiva functia executata functia nu primeste nici un argument se folosesc flag-urile implicite aici va fi stocat identificatorul firului

if (hThread[i] == NULL) ("Eroare la CreateThread\n"); ErrorExit } // asteptare terminare thread-uri for (i=0;i<NUMAR_FIRE;i++) WaitForSingleObject (hThread[i], INFINITE); // eliberare index TLS (dwTlsIndex); TlsFree return 0; }

Quiz
Pentru autoevaluare rspundei ntrebrile din acest quiz.

Exemplu

16

Exerciii
Warning
Pentru c nu ai parcurs nc noiunile necesare pentru a sincroniza thread-urile ntre ele, n cadrul acestui laborator vom folosi apeluri sleep() acolo unde e nevoie de sincronizare.

Prezentare
Pentru a urmri mai uor noiunile expuse la nceputul laboratorului folosii aceast prezentare (pdf) (odp).

Linux
Folosii macro-ul CHECK pentru a verifica valorile ntoarse de apelurile de sistem. 1. (1.5 puncte) ntreesere thread-uri (directorul lin/1-shared/ din arhiva de sarcini a laboratorului) Realizai un program care creeaz 2 thread-uri. Thread-urile create vor partaja un descriptor de fiiere, modificat de ctre fiecare din ele, la momente diferite. Thread-urile afieaz mesaje specifice la ieirea standard. Explicai succesiunea mesajelor. Programul principal va atepta ncheierea execuiei celor dou thread-uri. Hint-uri: Trebuie adugat codul de creare a thread-urilor i apelul ctre funcia care ntoarce id-ul thread-ului curent. Consultai seciunile Crearea firelor de execuie i Ateptarea firelor de execuie 2. (2 puncte) Thread Specific Data (directorul lin/2-time/ din arhiva de sarcini a laboratorului) Realizai un program care creeaz 2 thread-uri CPU-bound. Thread-urile calculeaz suma numerelor prime mai mici dect o limit dat (constanta MAX din schelet). Thread-urile vor stoca suma n TSD (Thread Specific Data) i o vor afia dup calcularea acesteia. Rezultatul ntors de fiecare thread este durata de execuie a calculului. Programul principal va afia timpul total de procesare (suma rezultatelor thread-urilor). Testai programului se va face cu ajutorul comenzii time (time ./time). Explicai diferena dintre durata afiat de program i timpii msurai folosind time. Hint-uri: Trebuie s completai codul de creare / ateptare a thread-urilor. Trebuie s obinei rezultatul ntors de fiecare thread. Pentru a folosi Thread Specific Data, consultai seciunea asociat Pentru a explica diferena dintre timpi, gndii-v ce ar putea genera overhead n programul respectiv. Formatul de afiare pentru "long long" atunci cnd folosii printf este "%lld". 3. (2.5 puncte) Thread workers (directorul lin/3-bgrep/ din arhiva de sarcini a laboratorului) Realizai un program numit "bgrep" (binary grep), care caut un caracter ntr-un fiier. Fiierul va fi mapat n ntregime n memorie, va fi mprit n buci, i fiecare bucat va fi atribuit unui thread. Exerciii 17

Rezultatul ntors de un thread va fi numrul de apariii ale caracterului n bucata atribuit thread-ului. Rezultatul final afiat va fi numrul de apariii ale caracterului n ntregul fiier (suma rezultatelor thread-urilor). Hint-uri: Trebuie s completai codul de creare / ateptare a thread-urilor. Calculai limitele bucilor de fiier pentru fiecare thread. Adunai numrul de apariii ntors de fiecare thread. Formula pentru chunk_size este corect; ncercai cteva exemple pentru a v convinge. De ce nu am mprit direct len la NUM_THREADS?

Windows
1. (1.5 puncte) ntreesere thread-uri (directorul win/1-shared/ din arhiva de sarcini a laboratorului) Creai 2 thread-uri: primul va aloca o zon de memorie i va scrie n ea, iar al doilea va citi din ea. Hint-uri: Trebuie s completai n schelet codul de creare al thread-urilor. Consultai seciunea dedicat crerii thread-urilor Observai c alocrile de memorie sunt partajate ntre thread-uri. 2. (1.5 puncte) Terminarea unui thread i ntoarcerea unei valori (directorul win/2-display/ din arhiva de sarcini a laboratorului) Creai un thread care primete ca parametru o structur, definit n schelet (struct ThreadParam). Thread-ul va afia vectorul din structura primit ca parametru, va calcula suma numerelor din acesta, i va ntoarce suma obinut. Programul principal afieaz suma ntoars de vector. Hint-uri: Folosii funcia DisplayParam (din schelet) pentru a afia vectorul. Pentru a verifica codul ntors de un thread, revedei seciunea relevant din laborator. 3. (2 puncte) Thread Local Storage (directorul win/3-sum/ din arhiva de sarcini a laboratorului) Creai 2 thread-uri care vor calcula suma numerelor dintr-un vector (primul thread, suma pentru prima jumtate; al doilea thread, suma pentru a doua jumtate). Suma va fi stocat in TLS (Thread Local Storage). Fiecare thread ntoarce suma pentru jumtatea corespunztoare, iar programul principal afieaz suma rezultatelor.

Soluii
Soluii exerciii laborator 8

Soluii

18

S-ar putea să vă placă și