Laboratoare:Thread-uri:Extra

= Linux =

Terminarea thread-urilor
Există și posibilitatea ca un fir de execuţie să termine un alt fir, folosind mecanismul de "thread cancellation". Pentru a face acest lucru se folosește funcţia pthread_cancel care primește ca parametru id-ul firului de execuţie ce urmează să fie terminat : Un thread unificabil care a fost terminat în acest mod trebuie așteptat folosind pthread_join pentru ca resursele folosite de el să fie eliberate. Un fir terminat cu pthread_cancel va întoarce valoarea PTHREAD_CANCELED. Totuși trebuie avut grijă la folosirea acestui mecanism, întrucât un thread este posibil să fie terminat înainte de a avea posibilitatea să elibereze anumite resurse folosite. Din aceasta cauză un thread poate să controleze dacă și când poate fi terminat de către un alt thread. Din punctul de vedere al posibilităţii terminării folosind pthread_cancel un thread poate fi :
 * cancelabil asincron - un astfel de fir poate fi terminat de către un alt fir în orice punct al execuţiei.
 * cancelabil sincron - un astfel de fir NU poate fi terminat în orice punct al execuţiei sale, ci numai în anumite puncte specifice.
 * necancelabil - un astfel de fir nu poate fi terminat folosind pthread_cancel.

Stabilirea tipului unui fir de execuţie din acest punct de vedere se face folosind funcţiile :

Funcţia pthread_setcancelstate poate fi folosită pentru a activa/dezactiva posibilitatea terminării unui fir folosind pthread_cancel. Argumentul state reprezintă noua lui stare care poate fi PTHREAD_CANCEL_ENABLE (pentru activare) sau PTHREAD_CANCEL_DISABLE (pentru dezactivare). În oldstate</tt>, dacă nu este NULL, se poate obţine vechea stare.

Folosind pthread_setcancelstate</tt> pot fi implementate regiuni critice, în sensul că tot codul dintr-o astfel de regiune trebuie executat în întregime sau deloc, practic firul să nu poată fi terminat de către un alt fir în timp ce se găsește într-o astfel de regiune.

Funcţia pthread_setcanceltype</tt> poate fi folosită pentru a schimba tipul de răspuns la o cerere de terminare : asincron sau sincron. Argumentul type</tt> indică noul tip și poate fi PTHREAD_CANCEL_ASYNCHRONOUS (pentru ca firul să poată fi terminat asincron, deci oricând) sau PTHREAD_CANCEL_DEFERRED (pentru ca o cerere de terminare sa fie amânată până când se ajunge într-un punct în care este posibilă terminarea firului). La fel, în oldtype</tt>, dacă nu este NULL se poate obţine starea anterioară.

În mod implicit la crearea unui fir folosind pthread_create</tt> starea lui este caracterizată de PTHREAD_CANCEL_ENABLE și PTHREAD_CANCEL_DEFERRED.

Funcţiile pthread_create</tt>, pthread_setcancelstate</tt> și pthread_setcanceltype</tt> întorc 0 în caz de succes și un cod de eroare nenul în caz de eșec.

În cazul în care un fir este cancelabil sincron, așa cum a fost precizat, terminarea lui se poate face numai în anumite puncte ale execuţiei sale. Practic cererile de terminare sunt amânate până se ajunge într-un astfel de punct, numit și cancellation point. Când se ajunge într-un astfel de punct se testează dacă există cereri de terminare, și dacă da, firul este terminat. Cel mai direct mod de a crea un astfel de punct este apelând funcţia : Dacă într-un program se folosește acest mecanism de terminare a firelor folosind pthread_cancel</tt>, această funcţie ar trebui apelată periodic în cadrul unei funcţii asociate unui thread în care se fac multe procesări, în punctele în care firul poate fi terminat fără a rămâne resurse neeliberate și fără a produce alte efecte nedorite.

Pe lângă pthread_testcancel</tt> mai există o serie de alte funcţii al căror apel reprezintă un punct de cancelare. Aceste funcţii sunt următoarele :
 * pthread_join</tt>
 * pthread_cond_wait</tt>
 * pthread_cond_timedwait</tt>
 * sem_wait</tt>
 * sigwait</tt>

De asemenea, orice funcţie care apelează una din aceste funcţii este și ea un astfel de punct de terminare.

În general nu este o idee foarte bună folosirea funcţiei <tt>pthread_cancel</tt> pentru a termina un thread, decât în cazuri excepţionale. În cazuri normale o strategie mai bună este de a indica firului că trebuie să se termine și apoi să se aștepte terminarea lui.

Lucrul cu atributele unui thread
Atributele unui fir de execuţie (cu o excepţie) sunt specificate la crearea firului de execuţie și nu pot fi schimbate pe perioada în care firul de execuţie este folosit.

Pentru iniţializarea și respectiv distrugerea unui obiect ce reprezintă atributele unui fir de execuţie avem la dispoziţie funcţiile : Pentru a stabili anumite atribute specifice ale unui fir, trebuie urmaţi câţiva pași :


 * 1) se creează un obiect de tipul <tt>pthread_attr_t</tt>, de exemplu declarând o variabilă de acest tip.
 * 2) se apelează funcţia <tt>pthread_attr_init</tt> căreia i se dă ca parametru un pointer la acest obiect. Această funcţie iniţializează atributele cu valorile lor implicite.
 * 3) se modifică obiectul ce contine atributele folosind una din funcţiile prezentate mai jos, pentru ca să se obţină atributele dorite.
 * 4) se transmite un pointer la aceste atribute funcţiei <tt>pthread_create</tt>.
 * 5) se apelează funcţia <tt>pthread_attr_destroy</tt> pentru a elibera obiectul ce reprezintă atributele (variabila de tip <tt>pthread_attr_t</tt> nu este însă dezalocată, ea poate fi refolosită utilizând <tt>pthread_attr_init</tt>).

Un același obiect de tip <tt>pthread_attr_t</tt> poate fi folosit pentru crearea mai multor fire de execuţie distincte și nu este necesar să fie păstrat după crearea acestora.

În continuare sunt prezentate funcţiile ce modifică atributele cele mai uzuale ale unui fir de execuţie.

Modificarea atributului detașabil/unificabil
Pentru a seta/verifica tipul unui fir de execuţie din punct de vedere detașabil/unificabil se pot utiliza următoarele funcţii : Primul parametru este un pointer la obiectul reprezentând atributele, iar al doilea parametru reprezintă starea dorită - unificabil/detașabil. Deoarece implicit sunt create fire unificabile (valoarea PTHREAD_CREATE_JOINABLE), această funcţie trebuie apelată doar dacă se creează fire detașabile și în acest caz al doilea parametru trebuie să aibă valoarea PTHREAD_CREATE_DETACHED.

Chiar dacă un fir de execuţie este creat unificabil, el poate fi transformat ulterior într-un fir detașabil apelând funcţia <tt>pthread_detach</tt>. Însă o data detașat, nu mai poate fi făcut unificabil la loc.

Modificarea politicii de alocare a procesorului
Standardul POSIX definește 3 politici de alocare a procesorului :


 * SCHED_RR - round robin
 * SCHED_FIFO - first in first out
 * SCHED_OTHER - implementare dependentă de sistem

Politicile SCHED_RR și SCHED_FIFO sunt opţionale și sunt suportate DOAR de firele de execuţie real time.

Funcţia care este folosită pentru a schimba politica de execuţie a firelor este : Pentru a afla politica curentă se poate folosi funcţia <tt>pthread_attr_getschedpolicy</tt> care în momentul de faţă întoarce doar SCHED_OTHER.

Modificarea priorităţii
Pentru a schimba/verifica prioritatea firelor de execuţie dispunem de următoarele funcţii : Prioritatea este semnificativă doar dacă politica folosită este SCHED_RR sau SCHED_FIFO.

Modificarea dimensiunii și adresei de start a stivei
De obicei stivele firelor de execuţie încep la limita unei pagini de memorie, iar orice dimensiune a lor este rotunjită până la limita următoarei pagini. La capătul stivei este adăugată de obicei o pagină pentru care nu avem drepturi de acces și astfel cele mai multe depășiri de stivă (stack overflows) vor genera semnalul SIGSEGV (deci un segmentation fault).

Dacă firul a fost creat unificabil stiva nu poate fi eliberată până nu se va termina un apel <tt>pthread_join</tt> pentru respectivul fir.

De obicei biblioteca de fire de execuţie alocă 1M de memorie virtuală pentru fiecare stivă de fir de execuţie.

Limita minimă pentru dimensiunea unei stive a unui fir de execuţie este PTHREAD_STACK_MIN.

Pentru a seta/afla dimensiunea stivei unui fir de execuţie se pot utiliza funcţiile : Pentru a specifica adresa de început a unei stive se poate utiliza funcţia :

= Windows =

Introducere
Comutarea între firele de execuţie în Windows este costisitoare pentru că presupune trecerea prin kernel-mode. De aceea au fost introduse fibrele (fibers), care sunt planificate în user space de către programele care le-au creat.

Fiecare fir de execuţie poate avea mai multe fibre, așa cum un proces poate avea mai multe fire de execuţie. O fibră se execută în contextul firului de execuţie care o planifică.

Sistemul de operare nu este conștient de existenţa fibrelor, el vede doar firul de execuţie în cadrul căruia fibrele există (o fibră împrumută identitatea firului de care aparţine). De exemplu dacă o fibră execută <tt>ExitThread</tt>, firul care a lansat fibra respectivă se va termina.

O fibră nu are asociate aceleași informaţii de stare ca și firul de execuţie, ci are asociate doar următoarele informaţii de stare :


 * stiva
 * un subset al regiștrilor firului de execuţie
 * datele fibrei furnizate la crearea fibrei

Cum se face planificarea?

Fibrele nu sunt planificate preemptiv (sunt lăsate să ruleze până cedează de buna voie procesorul), cu observaţia că dacă firul din care fac parte este preemptat, automat fibră în execuţie este preemptată. O fibră este planificată printr-o comutare către ea dintr-o altă fibră. Kernelul planifică fire de execuţie, nu fibre.

ConvertThreadToFiber
Înainte de a planifica prima fibră trebuie apelată funcţia <tt>ConvertThreadToFiber</tt> pentru a se crea o zonă în care să se salveze informaţii despre starea fibrei. După executarea acestei funcţii, firul apelant devine fibra activă (fibra în curs de execuţie). Informaţia de stare pentru această primă fibră include datele pasate ca argument funcţiei <tt>ConvertThreadToFiber</tt>.
 * <tt>lpParameter</tt> - [in] Pointer la o variabilă pasată fibrei. Fibra va putea obţine aceste date folosind macroul <tt>GetFiberData</tt>.

În caz de succes este returnată adresa fibrei. Altfel, rezultatul este NULL și tipul erorii se poate afla folosind <tt>GetLastError</tt>.

Există și funcţia cu efect contrar, <tt>ConvertFiberToThread</tt> :

Crearea unei fibre
Funcţia <tt>CreateFiber</tt> este folosită pentru a crea o nouă fibră dintr-una deja existentă (deci DUPĂ apelul <tt>ConvertThreadToFiber</tt>).

Aceasta funcţie creează un obiect de tip fibră, îi alocă o stivă și setează execuţia fibrei să înceapă la adresa de start specificată (funcţia fibrei). Această funcţie NU planifică fibra.
 * <tt>dwStackSize</tt> - dimensiunea, în bytes, a stivei.
 * <tt>lpStartAddress</tt> - pointer la funcţia ce trebuie executată de către fibră.
 * Atentie! Aceasta nu va fi executată decât după un apel <tt>SwitchToFiber</tt> către fibră.
 * Această funcţie arata așa :


 * unde <tt>FiberProc</tt> ţine locul numelui funcţiei, iar <tt>lpParameter</tt> este un pointer la datele fibrei, este același cu <tt>lpParameter</tt> transmis funcţiei <tt>CreateFiber</tt>.


 * <tt>lpParameter</tt> - pointer la o variabilă pasată fibrei. Fibra va putea obţine aceste date folosind macroul <tt>GetFiberData</tt>.

Numărul de fibre ce poate fi creat de către un fir de execuţie este limitat de către memoria virtuală disponibilă. Implicit, fiecare fibră are 1M rezervat pentru stivă, deci se pot crea cel mult 2028 fibre.

Planificarea unei fibre
Pentru a executa o fibra creată cu <tt>CreateFiber</tt> se folosește funcţia <tt>SwitchToFiber</tt>. Aceasta va salva starea fibrei curente și va restaura starea fibrei specificate. Se poate apela <tt>SwitchToFiber</tt> cu adresa unei fibre create de către un fir de execuţie diferit, pentru aceasta însă trebuie cunoscută adresa returnată celuilalt fir de execuţie de către funcţia <tt>CreateFiber</tt> și trebuie folosită o sincronizare adecvată. <tt>lpFiber</tt> reprezintă adresa fibrei ce este planificată.

Atentie! Apelul : poate provoca probleme neprevăzute.

Alte funcţii utile
<tt>GetFiberData</tt> poate fi folosită pentru a obţine datele asociate unei fibre (valoarea parametrului <tt>lpParameter</tt> din apelul uneia din funcţiile <tt>CreateFiber</tt> sau <tt>ConvertThreadToFiber</tt>). Aceeași valoare este primită ca parametru de către funcţia asociată fibrei. <tt>GetCurrentFiber</tt> poate fi utilizată pentru a obţine adresa fibrei, care este chiar rezultatul întors de <tt>CreateFiber</tt> sau <tt>ConvertThreadToFiber</tt>.

Fiber Local Storage
Se poate folosi <tt>Fiber Local Storage</tt> (FLS) pentru a crea o copie unică a unei variabile pentru fiecare fibră. Daca nu apare nici o comutare între fibre FLS se comportă exact ca și TLS. Funcţiile FLS (<tt>FlsAlloc</tt>, <tt>FlsFree</tt>, <tt>FlsGetValue</tt> și <tt>FlsSetValue</tt>) manipulează FLS-ul asociat fibrei curente. Dacă firul execută o fibră, și fibra se schimbă (prin comutare), atunci și FLS-ul va fi schimbat.

Funcţiile folosite pentru crearea, accesarea și distrugerea FLS-ului sunt similare cu cele pentru TLS.

ștergerea unei fibre
Pentru a șterge datele asociate unei fibre se folosește funcţia <tt>DeleteFiber</tt>. Aceste date includ stiva, un subset al regiștrilor și datele fibrei. Dacă o fibră în execuţie apelează <tt>DeleteFiber</tt>, firul asociat ei va apela <tt>ExitThread</tt> și se va termina. Totuși, dacă o fibra activă este ștearsă de către o altă fibră, firul care rulează fibra se va termina anormal, pentru că stiva fibrei (care este și a firului de execuţie) a fost eliberată. <tt>lpFiber</tt> specifică adresa fibrei ce va fi ștearsă.