Conceptul de thread (fir de execuție) definește cea mai mică unitate de procesare ce poate fi programată spre execuție de către sistemul de operare. Este folosit în programare pentru a eficientiza execuția programelor, executând porțiuni distincte de cod în paralel în interiorul aceluiași proces. Câteodata însă, aceste portiuni de cod care constituie corpul threadurilor, nu sunt complet independente și în anumite momente ale execuției, se poate întampla ca un thread să trebuiască să aștepte execuția unor instructiuni din alt thread, pentru a putea continua execuția propriilor instrucțiuni. Această tehnică, prin care un thread asteaptă execuția altor threaduri înainte de a continua propria execuție, se numește sincronizarea threadurilor.
Procese si thread-uri
Threadurile sunt diferite față de clasicele procese gestionte de sistemele de operare ce suportă multitasking, în principal prin faptul că, spre deosebire de procese, toate threadurile asociate unui proces folosesc același spațiu de adresare. Procesele sunt în general independente, în timp ce mai multe threaduri pot fi asociate unui unic proces. Procesele stochează un număr semnificativ de informații de stare, în timp ce threadurile dintr-un proces impart aceeași stare, memorie sau alte resurse.
Procesele pot interacționa numai prin mecanisme de comunicare interproces speciale oferite de sistemul de operare (semnale, semafoare, cozi de mesaje și altele asemenea). Cum împart același spațiu de adresare, threadurile pot comunica prin modificarea unor variabile asociate procesului și se pot sincroniza prin mecanismele proprii. În general este mult mai simplu și rapid schimbul de informații intre threaduri decât între procese.
Funcționarea firelor de execuție
Atât firele de execuție, cât și procesele au stări ce pot fi sincronizate pentru a evita problemele ce pot apărea datorita faptului că împart diverse resurse. În general, fiecare fir de execuție are o sarcină specifică și este programat astfel încât să optimizeze utilizarea procesorului.
Stările unui fir de execuție
Principalele stări ale unui fir de execuție sunt: Activ, Pregătit și Blocat. Comparativ cu acestea, procesele au și starea specifică Repaus (Sleep). Daca un proces este eliminat din memoria RAM, toate firele de execuție ale acestuia vor fi oprite, deoarece se aflau în același spațiu de memorie ca și procesul respectiv.
Schimbarea unei stări
Crearea: Atunci când un proces este pornit, se creează un thread pentru acel proces. Apoi, acel thread poate crea și gestiona mai multe threaduri, fiecare având pointeri către instrucțiunile specifice lui.
Blocarea: Atunci când un thread trebuie să aștepte altul, acesta este blocat - fiind salvat în stivă pointerul la acesta. Funcția de blocare se poate apela atât din interiorul cât și din exteriorul threadului.
Deblocarea: Un thread blocat poate fi deblocat în urma unei comenzi externe.
La alegere, în locul blocării/deblocării threadurilor, acestea pot primi priorități de execuție, însă această metodă este mai ineficientă, deoarece procesorul tot va comuta de la un thread la altul, operație ce va ocupa cicli de procesor. Cu varianta din urma însa, se vor evita mai ușor erori de programare prin care doua sau mai threaduri se vor aștepta între ele, blocând un proces întreg (deadlock).
Finalizarea: Atunci când un thread este finalizat, se va elibera memoria alocată acestuia.
Tipuri de fire de execuție
Există două tipuri principale de threaduri: la nivel de utilizator și la nivel de kernel[1]. O abordare combinată a celor două se numește proces de categorie ușoară (Lightweight ProcessesArhivat în , la Wayback Machine.), în care threadurile nucleului sistemului de operare sunt date spre procesare și care, la randul lor, gestionează firele de execuțile ale altor aplicații.
Threaduri la nivel de aplicație
Se mai numesc și fibre în sistemele de operare din familia Windows. Acestea sunt gestionate de codul aplicației și trecerea de la un thread la altul nu necesita apeluri la sistemul de operare sau întreruperi ale kernelului. De fapt, kernelul gestionează aceste threaduri ca procese diferite cu un singur fir de execuție.
Avantaje:
Pot fi implementate și în sisteme de operare care nu suportă aplicații multithreaded.
Reprezentare și gestionare usoară.
Trecerea de la un thread la altul se face printr-un simplu apel al unei proceduri.
Dezavantaje:
Lipsa unei coordonării între kernel și threaduri, deci nu poate fi controlată prioaritatea de procesare a acestora.
Blocarea unui thread ce apelează o funcție a kernelului poate duce la blocarea sistemului, chiar dacă în kernel există și alte procese ce pot fi rulate.
Threaduri la nivel de kernel
În acest caz, nucleul sistemului de operare gestionează funcționarea firelor de executie și, în locul unui tabel cu firele de execuție pentru fiecare proces în parte, sistemul deține un tabel ce pastrează toate threadurile din sistem.
Avantaje:
Datorită faptului că sistemul cunoaște toate threadurile, se va optimiza gestionarea acestora, prioritizând acele procese ce au mai multe threaduri.
Threadurile la nivel de kernel sunt recomandate pentru acele aplicații ce se blocheaza des.
Dezavantaje:
Sunt ineficiente și se procesează greoi - spre exemplu, operațiile ce țin de gestionarea threadurilor sunt de o suta de ori mai încete decât cele ale fibrelor.
Datorită faptului ca nucleul trebuie să gestioneze și procesele și threadurile, complexitatea kernelului va crește semnificativ.
Exemple de utilizare
API-ul POSIX în C
Crearea unui thread
Standardul POSIX 1003.1-2001[1] definește un APO utilizat în scrierea programelor multithreaded. Această interfață este cunoscută și sub numele de pthreads[2]. Un număr mare de sisteme de operare moderne includ o bibliotecă pentru programerea cu mai multe fire de execuție: Solaris (UI) threads, Win32 threads, DCE threads, DECthreads sau alte variații ale standardului pthreads. Există o tendință de a adopta chiar API-ul standard pthreads, pentru a ușura munca dezvoltatorilor în cazul migrarii de la o platforma la alta.
Codul iese din proces și distruge toate firele de execuție. Daca threadul curent este ultimul thread în execuție, atunci și procesul va fi oprit.
Alte limbaje de programare
În limbajul Java, threadurile sunt un element esențial pentru execuția paralelă, întrucât o aplicație ce rulează în mașina virtuală Java reprezintă un singur proces. Clasa java.lang.Thread poate fi utilizată pentru implementarea unui fir de execuție separat. Acțiunile ce se execută pe thread se pot defini în metoda run() a acestei clase (sau în cea a unui obiect ce implementează java.lang.Runnable și care este pasat ca argument constructorului clasei java.lang.Thread). Firul de execuție se pornește prin apelul metodei start() a acestuia.