Laboratoare:Thread-uri

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 execuţie (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 execuţie există în cadrul unui proces, și reprezintă o unitate de execuţie mai fină decât acesta. În momentul în care un proces este creat, în cadrul lui există un singur fir de execuţie, care execută programul secvenţial. Acest fir poate la rândul lui sa creeze alte fire de execuţie; aceste fire vor rula porţiuni ale binarului asociat cu procesul curent, posibil aceleaşi cu firul iniţial (care le-a creat).

Diferente dintre thread-uri şi procese

 * procesele nu partajează resurse între ele (decât dacă programatorul foloseşte un mecanism special pentru asta - vezi IPC), pe când 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 fişiere (aşadar, închiderea unui fişier este vizibilă imediat pentru toate thread-urile)
 * sockeţii
 * fiecare fir are un context de execuţie propriu, format din
 * stivă
 * set de regiştri (deci şi un contor de program - registrul (E)IP)

Procesele sunt folosite de SO pentru a grupa şi aloca resurse, iar firele de execuţie pentru a planifica execuţia de cod care accesează (în mod partajat) aceste resurse.

Avantajele thread-urilor
Deoarece thread-urile aceluiaşi proces folosesc toate spaţiul de adrese al procesului de care aparţin, folosirea lor are o serie de avantaje:
 * crearea/distrugerea unui thread durează mai puţin decât crearea/distrugerea unui proces
 * timpul context switch-ului între thread-urile aceluiaşi proces este foarte mic, întrucât nu e necesar să se "comute" şi spaţiul de adrese (pentru mai multe informaţii, căutaţi "TLB flush" pe google)
 * comunicarea între thread-uri are un overhead minim (practic se face prin modificarea unor zone de memorie din spaţiul de adresă)

Firele de execuţie se pot dovedi utile în multe situaţii, de exemplu, pentru a îmbunătăţi timpul de răspuns al aplicaţiilor cu interfeţe grafice (GUI), unde prelucrările CPU-intensive se fac de obicei într-un thread diferit de cel care afişează interfaţa.

De asemenea, ele simplifică structura unui program și conduc la utilizarea unui număr mai mic de resurse (pentru că nu mai este nevoie de diversele forme de IPC pentru a comunica).

Tipuri de thread-uri
Există 3 categorii de thread-uri :


 * Kernel Level Threads (KLT)
 * User Level Threads (ULT)
 * Fire de execuţie hibride

Kernel Level Threads
Managementul thread-urilor este făcut de kernel, şi programele user-space pot crea/distruge thread-uri printr-un set de apeluri de sistem. Kernel-ul menţine informaţii de context atât pentru procese cât și pentru thread-urile din cadrul proceselor, iar planificarea pentru execuţie se face la nivel de thread.

Avantaje :
 * dacă avem mai multe procesoare putem lansa în execuţie simultană mai multe thread-uri ale aceluiași 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 execuţie în kernel și apoi se mai face încă o schimbare din kernel în alt fir de execuţie, deci viteza de comutare este mică.

User Level Threads
Kernel-ul nu este conștient de existenţa lor, şi managementul lor este făcut de procesul în care ele există, folosind de obicei o bibliotecă. Astfel, schimbarea contextului nu necesită intervenţia kernel-ului, iar algoritmul de planificare depinde de aplicaţie.

Avantaje :
 * schimbarea de context nu implică kernelul, deci avem o comutare rapidă
 * planificarea poate fi aleasă de aplicaţie si deci se poate alege una care să favorizeze creşterea vitezei aplicaţiei 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 aplicaţie 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 "văzut" de kernel, ele nu vor putea folosi eficient resursele sistemului (vor împărţi amândouă un acelaşi procesor).

Fire de execuţie 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 unităţile 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 numărul de KLT alocate unui proces.

Suport POSIX
În ceea ce privește thread-urile, POSIX nu specifică dacă acestea trebuie implementate în user-space sau kernel-space. Linux le implementează în kernel-space, dar nu diferenţiază thread-urile de procese decât prin faptul că thread-urile partajează spaţiul de adresă (atât thread-urile, cât şi procesele, sunt un caz particular de "task"). Pentru folosirea thread-urilor în Linux trebuie să includem header-ul pthread.h unde se găsesc declaraţiile funcţiilor și tipurilor de date necesare şi să utilizăm biblioteca libpthread.

Crearea firelor de execuţie
Pentru crearea unui nou fir de execuţie se foloseste funcţia pthread_create : Noul fir creat se va executa concurent cu firul de execuţie din care a fost creat. Acesta va executa codul specificat de funcţia start_routine căreia i se va pasa argumentul arg. Folosind arg se poate transmite firului de execuţie un pointer la o structură care sa conţină toţi "parametrii" necesari acestuia.

Prin parametrul tattr se stabilesc atributele noului fir de execuţie. Dacă transmitem valoarea NULL thread-ul va fi creat cu atributele implicite

Așteptarea firelor de execuţie
La fel ca la procese, un părinte își poate aștepta fiul apelând pthread_join (înlocuiește waitpid). Primul parametru specifică identificatorul firului de execuţie așteptat, iar al doilea parametru specifică unde se va plasa codul întors de funcţia copil (printr-un pthread_exit</tt> sau printr-un return</tt>).

În caz de succes se întoarce valoarea 0, altfel se întoarce o valoare negativă reprezentând un cod de eroare.

Thread-urile se împart în două categorii :


 * unificabile :
 * permit unificarea cu alte threaduri care apelează pthread_join</tt>.
 * resursele ocupate de thread nu sunt eliberate imediat după terminarea threadului, ci mai sunt păstrate până când un alt thread va executa pthread_join</tt> (analog cu procesele zombie)
 * threadurile sunt implicit unificabile


 * detaşabile
 * un thread este detaşabil dacă :
 * a fost creat detaşabil.
 * i s-a schimbat acest atribut în timpul execuţiei prin apelul pthread_detach</tt>.
 * nu se poate executa un pthread_join</tt> pe ele
 * vor elibera resursele imediat ce se vor termina (analog cu ignorarea semnalului SIGCHLD în părinte atunci când se termină procesele copil)

Terminarea firelor de execuţie
Un fir de execuţie se termină la un apel al funcţiei pthread_exit</tt> : Dacă nu există un astfel de apel este adăugat unul, în mod automat, la sfârșitul codului firului de execuţie.

Prin parametrul retval</tt> se comunică părintelui un mesaj despre modul de terminare al copilului. Această valoare va fi preluată de funcţia pthread_join</tt>.

Metodele ca un fir de execuţie 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</tt>. Totuşi, această metodă nu este recomandată, pentru că este greoaie, şi pune probleme foarte delicate la clean-up. Pentru mai multe detalii, consultaţi 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 fiecărui thread al procesului curent. Cheia are acelaşi rol pe care o are numele unei variabile: desemnează locaţia de memorie la care se află valoarea.

Fiecare thread va avea propria copie a unei "variabile" corespunzătoare unei chei k</tt>, pe care o poate modifica, fără ca acest lucru să fie observat de celelalte thread-uri, sau să necesite sincronizare. De aceea, TSD este folosită uneori pentru a optimiza operaţiile care necesită multă sincronizare între thread-uri: fiecare thread calculează informaţia specifică, şi există un singur pas de sincronizare la sfârşit, necesar pentru reunirea rezultatelor tuturor thread-urilor.

Cheile sunt de tipul pthread_key_t</tt>, iar valorile asociate cu ele, de tipul generic void*</tt> (pointeri către locaţia de pe stivă unde este memorată variabila respectivă). Descriem în continuare operaţiile disponibile cu variabilele din TSD:

Crearea şi ştergerea unei variabile
O variabilă se crează folosind:

Al doilea parametru reprezintă o funcţie de cleanup. Acesta poate avea una din valorile:
 * NULL, şi este ignorat
 * pointer către o funcţie de clean-up care se execută la terminarea thread-ului

Pentru ştergerea unei variabile se apelează: Ea nu apelează funcţia de cleanup asociată acesteia.

Modificarea şi citirea unei variabile
După crearea cheii, fiecare fir de execuţie poate modifica propria copie a variabilei asociate folosind funcţia pthread_setspecific</tt> : Primul parametru reprezintă cheia, iar al doilea parametru reprezintă valoarea specifică ce trebuie stocată și care este de tipul void*</tt>.

Pentru a determina valoarea unei variabile de tip TSD se folosește funcţia :

Funcţii pentru cleanup
Funcţiile de cleanup asociate TSD-urilor pot fi foarte utile pentru a asigura faptul că resursele sunt eliberate atunci când un fir se termină singur sau este terminat de către un alt fir. Uneori poate fi util să se poată specifica astfel de funcţii fără a crea neapărat un thread specific data. Pentru acest scop exista funcţiile de cleanup.

O astfel de funcţie de cleanup este o funcţie care este apelată când un thread se termină. Ea primește un singur parametru de tipul void *</tt> care este specificat la înregistrarea funcţiei.

O funcţie de cleanup este folosită pentru a elibera o resursă numai în cazul în care un fir de execuţie apelează <tt>pthread_exit</tt> sau este terminat de un alt fir folosind <tt>pthread_cancel</tt>. În circumstanţe normale, atunci când un fir nu se termină în mod forţat, resursa trebuie eliberată explicit, iar funcţia de cleanup trebuie sa fie scoasă.

Pentru a înregistra o astfel de funcţie de cleanup se folosește : Aceasta funcţie primește ca parametri un pointer la funcţia care este înregistrată și valoarea argumentului care va fi transmis acesteia. Funcţia <tt>routine</tt> va fi apelată cu argumentul <tt>arg</tt> atunci când firul este terminat forţat. Daca sunt înregistrate mai multe funcţii de cleanup, ele vor fi apelate în ordine LIFO (cea mai recent instalată va fi prima apelată).

Pentru fiecare apel <tt>pthread_cleanup_push</tt> trebuie să existe și apelul corespunzător <tt>pthread_cleanup_pop</tt> care deînregistrează o funcţie de cleanup : Această funcţie va deînregistra cea mai recent instalată funcţie de cleanup, și dacă parametrul <tt>execute</tt> este nenul o va și executa.

Atentie! Un apel <tt>pthread_cleanup_push</tt> trebuie să aibă un apel corespunzător <tt>pthread_cleanup_pop</tt> în aceeași funcţie și la același nivel de imbricare.

Un mic exemplu de folosire a funcţiilor de cleanup :

Atributele unui thread
Atributele reprezintă o modalitate de specificare a unui comportament diferit de comportamentul implicit. Atunci când un fir de execuţie este creat cu <tt>pthread_create</tt> se poate specifica un atribut pentru respectivul fir de execuţie. Atributele implicite sunt suficiente pentru marea majoritate a aplicaţiilor. Cu ajutorul unui atribut se pot schimba:
 * starea: unificabil sau detaşabil
 * 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 puteţi găsi la secţiunea suplimentară dedicată.

Cedarea procesorului
Un thread cedează dreptul de executie unui alt thread, în urma unuia din următoarele evenimente:

Dacă există alte procese interesate de procesor acesta li se oferă, iar dacă nu există nici un alt proces în așteptare pentru procesor, firul curent își continuă execuţia.
 * 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 către kernel
 * cedează voluntar dreptul, folosind funcţia:

Alte operaţii
Dacă dorim să fim siguri că un cod de iniţializare se execută o singură dată putem folosi funcţia : Scopul funcţiei <tt>pthread_once</tt> este de a asigura că o bucată de cod (de obicei folosită pentru iniţializări) se execute o singură dată. Argumentul <tt>once_control</tt> este un pointer la o variabilă iniţializată cu PTHREAD_ONCE_INIT. Prima oară când această funcţie este apelată ea va apela funcţia <tt>init_routine</tt> și va schimba valoarea variabilei <tt>once_control</tt> pentru a ţine minte că iniţializarea a avut loc. Următoarele apeluri ale acestei funcţii cu același <tt>once_control</tt> nu vor face nimic.

Funcţia <tt>pthread_once</tt> întoarce întotdeauna 0.

Pentru a determina identificatorul thread-ului curent se poate folosi funcţia : Pentru a determina dacă doi identificatori se referă la același thread se poate folosi : Pentru aflarea/modificarea priorităţilor sunt disponibile următoarele apeluri :

Compilare
La compilare trebuie specificată și biblioteca libpthread (deci se va folosi argumentul -lpthread).

Atentie! Nu link-aţi un program single-threaded cu această bibliotecă. Daca faceţi așa ceva se vor stabili niște mecanisme multithreading care vor fi iniţializate la execuţie. 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 execuţie, fiecare afișând un caracter de un anumit număr de ori pe ecran.

Comanda utilizată pentru a compila acest exemplu va fi:

Crearea firelor de execuţie
Pentru a lansa un nou fir de execuţie există funcţiile <tt>CreateThread</tt> și <tt>CreateRemoteThread</tt> (a doua fiind folosită pentru a crea un fir de execuţie în cadrul altui proces decât cel curent).

<tt>dwStackSize</tt> reprezintă mărimea iniţială a stivei, în bytes. Sistemul rotunjește această valoare la cel mai apropiat multiplu de dimensiunea unei pagini. Dacă parametrul este 0, noul thread va folosi mărimea implicită. <tt>lpStartAddress</tt> este un pointer la funcţia ce trebuie executată de către thread. Această funcţie are următorul prototip: unde <tt>lpParameter</tt> reprezintă datele care sunt pasate firului de execuţie la execuţie. La fel ca pe Linux, se poate transmite un pointer la o structură, care conţine toţi parametrii necesari. Rezultatul întors poate fi obţinut de un alt thread folosind funcţia <tt>GetExitCodeThread</tt>.

Un mic exemplu : La crearea unui nou fir de execuţie parametrii cei mai importanţi sunt funcţia pe care acesta o va executa şi parametrul care este pasat acesteia.

Handle și identificator
Thread-urile pot fi identificate în sistem în 3 moduri:
 * printr-un <tt>HANDLE</tt>, obţinut la crearea thread-ului, sau folosind funcţia <tt>OpenThread</tt>, căreia i se dă ca parametru identificatorul thread-ului:
 * printr-un pseudo-HANDLE, o valoare specială care indică funcţiilor de lucru cu HANDLE-uri că este vorba de HANDLE-ul asociat cu thread-ul curent (obţinut, de exemplu, apelând <tt>GetCurrentThread</tt>). Pentru a converti un pseudo-HANDLE într-un HANDLE veritabil, trebuie folosită funcţia <tt>DuplicateHandle</tt>. De asemenea, nu are sens să facem <tt>CloseHandle</tt> pe un pseudo-HANDLE. Pe de altă parte, handle-ul obţinut cu <tt>DuplicateHandle</tt> trebuie inchis daca nu mai este nevoie de el.
 * printr-un identificator de thread, de tipul DWORD, întors la crearea thread-ului, sau obţinut folosind <tt>GetCurrentThreadId</tt>. O diferenţă dintre identificator şi HANDLE este faptul că nu trebuie să ne preocupăm sa închidem un identificator, pe când la HANDLE, pentru a evita leak-urile, trebuie să apelăm <tt>CloseHandle</tt>

Handle-ul obţinut la crearea unui thread are implicit drepturi de acces nelimitate. El poate fi moştenit sau nu de procesele copil ale procesului curent în funcţie de flag-urile specificate la crearea lui. Prin funcţia <tt>DuplicateHandle</tt>, se poate crea un nou handle cu mai puţine drepturi. Handle-ul este valid până ce este închis, chiar dacă firul de execuţie pe care îl reprezintă s-a terminat.

Aşteptarea firelor de execuţie
Pe Windows, se poate aştepta terminarea unui fir de execuţie folosind aceeaşi funcţie ca pentru aşteptarea oricărui obiect de sincronizare:

Terminarea firelor de execuţie
Un fir de execuţie se termină în unul din următoarele cazuri :
 * el însuși apelează funcţia <tt>ExitThread</tt> :
 * funcţia asociată firului de execuţie execută un <tt>return</tt>.
 * un fir de execuţie ce deţine un handle cu dreptul <tt>THREAD_TERMINATE</tt> asupra firului de execuţie, execută un apel <tt>TerminateThread</tt> pe acest handle :
 * unde <tt>dwExitCode</tt> specifică codul de terminare al threadului.


 * sau întregul proces se termină ca urmare a unui apel <tt>ExitProcess</tt> sau <tt>TerminateProcess</tt>.

Pentru aflarea codului de terminare a unui fir de execuţie folosim funcţia <tt>GetExitCodeThread</tt>, și acest cod poate fi:
 * <tt>STILL_ACTIVE</tt> daca firul de execuţie nu s-a terminat.
 * valoarea întoarsă de funcţia asociată firului de execuţie.
 * valoarea specificată la apelul uneia din funcţiile <tt>TerminateThread</tt>, <tt>TerminateProcess</tt>, <tt>ExitThread</tt> sau <tt>ExitProcess</tt>.

Atenţie! Funcţiile <tt>TerminateThread</tt> și <tt>TerminateProcess</tt> nu trebuie folosite decât în cazuri extreme (pentru că nu eliberează resursele folosite de firul de execuţie, iar unele resurse pot fi VITALE). Metoda preferată de a termina un fir de execuţie este <tt>ExitThread</tt>, sau folosirea unui protocol de oprire între thread-ul care doreşte să închidă un alt thread şi thread-ul care trebuie oprit.

La terminarea ultimului fir de execuţie al unui proces se termină și procesul.


 * <tt>hThread</tt> - handle la firul de execuţie în discuţie ce trebuie să aibă dreptul de acces <tt>THREAD_QUERY_INFORMATION</tt>.
 * <tt>lpExitCode</tt> - pointer la o variabilă în care va fi plasat codul de terminare al firului. Dacă firul nu și-a terminat execuţia, această valoare va fi <tt>STILL_ACTIVE</tt>.
 * Atentie! Pot apărea probleme dacă firul de execuţie returnează chiar <tt>STILL_ACTIVE</tt> (259), și anume aplicaţia care testează valoarea poate intra într-o buclă infinită.

Dacă funcţia se termină cu succes va întoarce o valoare nenulă. Altfel întoarce 0, iar eroarea poate fi aflată folosind <tt>GetLastError</tt>.

Suspend, Resume
Prin intermediul acestor două funcţii un fir de execuţie poate suspenda/relua execuţia unui alt fir de execuţie.

Un fir de execuţie suspendat nu mai este planificat pentru a obţine timp pe procesor.

Cele doua funcţii manipulează un contor de suspendare (prin incrementare, respectiv decrementare - în limitele 0 - <tt>MAXIMUM_SUSPEND_COUNT</tt>).

În cazul în care contorul de suspendare este mai mare strict decât 0, firul de execuţie este suspendat.

Un fir de execuţie poate fi creat în starea suspendat folosind flag-ul <tt>CREATE_SUSPENDED</tt>.

Aceste funcţii nu pot fi folosite pentru sincronizare (pentru ca nu controlează punctul în care firul de execuţie își va suspenda execuţia), dar sunt utile pentru programe de debug.

Cedarea procesorului
Un fir de execuţie poate renunţa de bună voie la procesor.

În urma apelului funcţiei <tt>Sleep</tt> un fir de execuţie este suspendat pentru cel puţin o anumită perioadă de timp (<tt>dwMilliseconds</tt>). Există de asemenea funcţia <tt>SleepEx</tt> care este un <tt>Sleep</tt> alertabil (ceea ce înseamnă că se pot prelucra APC-uri - Asynchronous Procedure Call - pe durata execuţiei lui SleepEx).

Funcţia <tt>SwitchToThread</tt> este asemănătoare cu <tt>Sleep</tt> doar că nu este specificat intervalul de timp, astfel firul de execuţie renunţă doar la timpul pe care îl avea pe procesor în momentul respectiv (time-slice). Funcţia întoarce <tt>TRUE</tt> dacă procesorul este cedat unui alt thread şi <tt>FALSE</tt> dacă nu există alte thread-uri gata de execuţie.

Alte funcţii utile
Rezultatul este un pseudohandle pentru firul curent ce nu poate fi folosit decât de firul apelant. Acest handle are maximum de drepturi de acces asupra obiectului pe care îl reprezintă. Rezultatul este identificatorul firului de execuţie. Rezultatul este identificatorul firului ce corespunde handle-ului <tt>Thread</tt>.

Thread Local Storage
Ca și în Linux, și în Windows există un mecanism prin care fiecare fir de execuţie să aibă anumite date specifice. Acest mecanism poartă numele de thread local storage (TLS). În Windows, pentru a accesa datele din TLS se folosesc indecșii asociaţi acestora (corespunzători cheilor din Linux).

Pentru a crea un nou TLS se apelează funcţia : Funcţia întoarce în caz de succes indexul asociat TLS-ului, prin intermediul căruia fiecare fir de execuţie va putea accesa datele specifice.

În caz de eșec funcţia întoarce valoarea <tt>TLS_OUT_OF_INDEXES</tt>.

Pentru a stoca o nouă valoare într-un TLS se folosește funcţia :

Un thread poate afla valoarea specifică lui dintr-un TLS apelând funcţia : unde <tt>dwTlsIndex</tt> este indexul asociat TLS-ului, alocat cu <tt>TlsAlloc</tt>.

În caz de succes funcţia întoarce valoarea stocată în TLS, iar în caz de eșec întoarce 0. Dacă data stocată în TLS are valoarea 0 atunci valoarea întoarsă este tot 0, dar <tt>GetLastError</tt> va întoarce <tt>NO_ERROR</tt>. Deci trebuie verificată eroarea întoarsă de <tt>GetLastError</tt>.

Pentru a elibera un index asociat unui TLS se folosește funcţia : unde <tt>dwTlsIndex</tt> este indexul asociat TLS-ului.

Dacă firele de execuţie au alocat memorie și au stocat în TLS un pointer la memoria alocată, această funcţie nu va face dezalocarea memoriei. Memoria trebuie dezalocată de către fire înainte de apelul lui <tt>TlsFree</tt>.

Fibre de execuţie
Windows pune la dispoziţie şi o implementare de user-space threads, numite <tt>fibre</tt>. Kernel-ul planifică un singur KLT asociat cu un set de fibre, iar fibrele colaborează pentru a partaja timpul de procesor oferit acestuia. Deşi viteza de execuţie este mai bună (pentru context-switch, nu mai este necesară interacţiunea cu kernel-ul), programele scrise folosind fibre pot deveni complexe. Mai multe informaţii puteţi găsi la secţiunea suplimentară dedicată.

Securitate și drepturi de acces
Modelul de securitate Windows NT ne permite să controlăm accesul la obiectele de tip fir de execuţie.

Descriptorul de securitate pentru un fir de execuţie se poate specifica la apelul uneia dintre funcţiile <tt>CreateProcess</tt>, <tt>CreateProcessAsUser</tt>, <tt>CreateProcessWithLogonW</tt>, <tt>CreateThread</tt> sau <tt>CreateRemoteThread</tt>.

Dacă în locul acestui descriptor este pasată valoarea NULL, firul de execuţie va avea un descriptor implicit.

Pentru a obţine acest descriptor este folosită funcţia <tt>GetSecurityInfo</tt>, iar pentru a-l schimba funcţia <tt>SetSecurityInfo</tt>.

Handle-ul întors de funcţia <tt>CreateThread</tt> are <tt>THREAD_ALL_ACCESS</tt>. La apelul <tt>GetCurrentThread</tt>, sistemul întoarce un pseudohandle cu maximul de drepturi de acces pe care descriptorul de securitate al firului de execuţie îl permite apelantului.

Drepturile de acces pentru un obiect fir de execuţie includ drepturile de acces standard : <tt>DELETE</tt>, <tt>READ_CONTROL</tt>, <tt>SYNCHRONIZE</tt>, <tt>WRITE_DAC</tt> și <tt>WRITE_OWNER</tt> la care se adaugă drepturi specifice, pe care le puteţi găsi pe MSDN.

Exemplu
Exemplul prezintă crearea a 2 fire de execuţie ce vor folosi un TLS.

Quiz
Pentru autoevaluare răspundeți întrebările din acest quiz.

Warning
Pentru că nu aţi parcurs încă noţiunile necesare pentru a sincroniza thread-urile între ele, în cadrul acestui laborator vom folosi apeluri <tt>sleep</tt> acolo unde e nevoie de sincronizare.

Prezentare
Pentru a urmări mai ușor noțiunile expuse la începutul laboratorului folosiți această prezentare (pdf) (odp).

Linux
Folosiţi macro-ul CHECK pentru a verifica valorile întoarse de apelurile de sistem.


 * 1) (1.5 puncte) Întrețesere thread-uri (directorul <tt>lin/1-shared/</tt> din arhiva de sarcini a laboratorului)
 * 2) * Realizaţi un program care creează 2 thread-uri.
 * 3) * Thread-urile create vor partaja un descriptor de fișiere, modificat de către fiecare din ele, la momente diferite.
 * 4) * Thread-urile afișează mesaje specifice la ieșirea standard. Explicați succesiunea mesajelor.
 * 5) * Programul principal va aștepta încheierea execuției celor două thread-uri.
 * Hint-uri:
 * 1) ** Trebuie adăugat codul de creare a thread-urilor și apelul către funcția care întoarce id-ul thread-ului curent.
 * 2) ** Consultați secțiunile Crearea firelor de execuție și Așteptarea firelor de execuție
 * 3) (2 puncte) Thread Specific Data (directorul <tt>lin/2-time/</tt> din arhiva de sarcini a laboratorului)
 * 4) * Realizaţi un program care creează 2 thread-uri CPU-bound.
 * 5) * Thread-urile calculează suma numerelor prime mai mici decât o limită dată (constanta <tt>MAX</tt> din schelet).
 * 6) * Thread-urile vor stoca suma în TSD (Thread Specific Data) și o vor afișa după calcularea acesteia.
 * 7) * Rezultatul întors de fiecare thread este durata de execuție a calculului.
 * 8) * Programul principal va afișa timpul total de procesare (suma rezultatelor thread-urilor).
 * 9) * Testați programului se va face cu ajutorul comenzii <tt>time</tt> (<tt>time ./time</tt>). Explicaţi diferența dintre durata afișată de program și timpii măsurați folosind <tt>time</tt>.
 * Hint-uri:
 * 1) ** Trebuie să completați codul de creare / aşteptare a thread-urilor.
 * 2) ** Trebuie să obţineţi rezultatul întors de fiecare thread.
 * 3) ** Pentru a folosi Thread Specific Data, consultați secţiunea asociată
 * 4) ** Pentru a explica diferența dintre timpi, gândiți-vă ce ar putea genera overhead în programul respectiv.
 * 5) ** Formatul de afişare pentru "long long" atunci când folosiţi printf este "%lld".
 * 6) (2.5 puncte) Thread workers (directorul <tt>lin/3-bgrep/</tt> din arhiva de sarcini a laboratorului)
 * 7) * Realizaţi un program numit "bgrep" (binary grep), care caută un caracter într-un fişier.
 * 8) * Fişierul va fi mapat în întregime în memorie, va fi împărţit în bucăți, şi fiecare bucată va fi atribuită unui thread.
 * 9) * Rezultatul întors de un thread va fi numărul de apariții ale caracterului în bucata atribuită thread-ului.
 * 10) * Rezultatul final afișat va fi numărul de apariții ale caracterului în întregul fişier (suma rezultatelor thread-urilor).
 * Hint-uri:
 * 1) ** Trebuie să completaţi codul de creare / aşteptare a thread-urilor.
 * 2) ** Calculaţi limitele bucăţilor de fişier pentru fiecare thread.
 * 3) ** Adunaţi numărul de apariţii întors de fiecare thread.
 * 4) ** Formula pentru chunk_size este corectă; încercaţi câteva exemple pentru a vă convinge. De ce nu am împărţit direct <tt>len</tt> la <tt>NUM_THREADS</tt>?

Windows

 * 1) (1.5 puncte) Întrețesere thread-uri (directorul <tt>win/1-shared/</tt> din arhiva de sarcini a laboratorului)
 * 2) * Creaţi 2 thread-uri: primul va aloca o zonă de memorie și va scrie în ea, iar al doilea va citi din ea.
 * Hint-uri:
 * 1) ** Trebuie să completați în schelet codul de creare al thread-urilor.
 * 2) ** Consultați secţiunea dedicată creării thread-urilor
 * 3) ** Observați că alocările de memorie sunt partajate între thread-uri.
 * 4) (1.5 puncte) Terminarea unui thread și întoarcerea unei valori (directorul <tt>win/2-display/</tt> din arhiva de sarcini a laboratorului)
 * 5) * Creaţi un thread care primeşte ca parametru o structură, definită în schelet (<tt>struct ThreadParam</tt>).
 * 6) * Thread-ul va afișa vectorul din structura primită ca parametru, va calcula suma numerelor din acesta, și va întoarce suma obținută.
 * 7) * Programul principal afișează suma întoarsă de vector.
 * Hint-uri:
 * 1) ** Folosiţi funcţia <tt>DisplayParam</tt> (din schelet) pentru a afişa vectorul.
 * 2) ** Pentru a verifica codul întors de un thread, revedeţi secţiunea relevantă din laborator.
 * 3) (2 puncte) Thread Local Storage (directorul <tt>win/3-sum/</tt> din arhiva de sarcini a laboratorului)
 * 4) * Creaţi 2 thread-uri care vor calcula suma numerelor dintr-un vector (primul thread, suma pentru prima jumătate; al doilea thread, suma pentru a doua jumătate).
 * 5) * Suma va fi stocată in TLS (Thread Local Storage).
 * 6) * Fiecare thread întoarce suma pentru jumătatea corespunzătoare, iar programul principal afișează suma rezultatelor.

= Soluţii = Soluţii exerciţii laborator 8