Sunteți pe pagina 1din 12

PTHREADS.

Planificarea thread-urilor
1. Introducere
Scop
Scopul acestei lucrri este de prezenta funciile ce pot fi utilizate pentru a stabili
modul de planificare a unui thread, n contextul execuiei concurente a mai
multor thread-uri.
Obiective
Obiectivele ce se dorete a fi atinse prin aceast prezentare sunt:
1. cunoaterea i nelegerea modalitilor de planificare a unui thread
n Linux i a caracteristicilor fiecreia dintre ele;
2. cunoaterea funciilor prin care se stabilete modul de planificare a
thread-urilor;
3. identificarea unor situaii concrete n care posibilitatea de a controla,
pe ct posibil, planificarea thread-urilor poate fi util.
Prezentare general
Planificarea thread-urilor sau proceselor const n strategia folosit de ctre
sistemul de operare pentru a decide la un moment dat care thread trebuie
executat i pentru ct timp. Componenta sistemului de operare care
realizeaz planificarea se numete planificatorul de thread-uri sau procese.
Sistemul de operare Linux folosete un mecanism de planificare bazat pe
prioriti. Fiecrui thread i este asociat o prioritate, fiind ales ntotdeauna
pentru execuie thread-ul cu prioritatea cea mai mare. Corespunztor fiecrei
prioriti se menine o list de thread-uri care candideaz pentru obinerea
procesorului. Rezultatul deciziei de planificare, este controlat prin
implementarea a trei strategii diferite de inserare a thread-urilor n listele de
prioriti. Dintre cele trei strategii, numite politici de planificare, dou sunt
destinate planificrii thread-urilor de prioritate ridicat, ce aparin aplicaiilor
de timp real, iar cea de-a treia este destinat planificrii thread-urilor
aplicaiilor obinuite, de prioritate mic.
Lucrarea de fa descrie cele trei politici de planificare i prezint setul de
funcii destinate stabilirii i modificrii parametrilor ce influeneaz planificarea
unui thread, adic prioritatea i politica de planificare.
1

2. Prioritatea i politica de planificare


Exist dou atribute ale unui thread care fac ca respectivul thread s fie
tratat ntr-un mod special de ctre planificatorul de thread-uri. Aceste
atribute sunt prioritatea i politica de planificare.
Pe baza prioritii se face diferenierea ntre thread-uri n ceea ce privete
planificarea lor pentru execuie. Politica de planificare este strategia ce
definete modul n care thread-urile cu aceeai prioritate sunt executate pe
procesoarele disponibile. n Linux exist posibilitatea stabilirii a trei politici
de planificare, una pentru thread-urile aplicaiilor obinuite i celelalte dou
pentru aplicaiile de timp real. Fiecrui thread i se asociaz o prioritate static,
avnd valoarea cuprins ntre 0 i 99, valoare ce poate fi modificat doar cu
ajutorul unor anumite funcii. Corespunztor fiecrei prioriti posibile,
planificatorul de thread-uri din Linux menine cte o list a thread-urilor
active avnd acea valoare a prioritii statice. Planificatorul va alege
ntotdeauna pentru execuie un thread din lista corespunztoare celei mai mari
prioriti pentru care exist thread-uri active. Politica de planificare determin
pentru fiecare thread modul n care el este inserat i avanseaz n lista
corespunztoare prioritii statice pe care o are asociat.
Politica de planificare
Acestui atribut al thread-ului i se poate atribui o anumit valoare ntreag, sub
forma unor constante predefinite, corespunztor uneia dintre cele trei politici
de planificare posibile. Numele constantelor, precum i caracteristicile
fiecrei politici de planificare sunt:
SCHED_FIFO: este o politic disponibil pentru thread-urile
aplicaiilor de timp real i funcioneaz pe baza principiului primul
sosit, primul servit FIFO. Odat ales un thread pentru execuie,
acesta nu poate fi ntrerupt, dect dac devine activ un alt thread cu
prioritatea mai mare dect a lui, dac execut o instruciune care l
pune n stare de ateptare (de exemplu o instruciune de I/O) sau
dac n mod voluntar cedeaz procesorul prin apelul funciei
sched_yield. n primul caz, thread-ul este pus la nceputul listei
ataate prioritii pe care o are, iar n celelalte dou la sfritul listei.
SCHED_RR: funcioneaz similar cu politica SCHED_FIFO, dar
unui thread avnd aceast politic de planificare i se poate aloca
2

procesorul pentru execuie doar pe durata unei cuante de timp


fixate. n momentul expirrii cuantei, thread-ul este ntrerupt i pus
la sfritul listei ataate prioritii pe care o are thread-ul.
SCHED_OTHER: reprezint politica de planificare a thread-urilor
obinuite. Thread-urile avnd asociat aceast politic de planificare
pot avea doar valoarea 0 a prioritii statice, fiind pstrate ntr-o
singur list din care sunt alese pentru execuie pe baza principiului
de time-sharing i prin calcularea dinamic a unor prioriti
specifice doar thread-urilor din cadrul listei respective.

Valoarea implicit a parametrului ce descrie politica de planificare a unui


thread este SCHED_OTHER.
Toate politicile de planificare prezentate sunt preemtive, adic dac la un
moment dat devine activ (e inserat ntr-una din listele meninute de
planificator) un thread mai prioritar dect cel curent, acesta din urm este
ntrerupt i procesorul este alocat thread-ului cu prioritatea mai mare.
Prioritatea de planificare
Valoarea pe care o poate primi prioritatea static a unui thread depinde de
politica de planificare stabilit pentru acel thread. Acest atribut este vzut ca
un parametru al politicii de planificare. Intervalul n care se situeaz
prioritatea unui thread este definit de o valoare minim, respectiv maxim.
n general, aceste limite sunt 1 i, respectiv 99, ns valoarea lor efectiv
pentru o anumit politic de planificare se poate obine cu ajutorul funciilor
de mai jos.
#include <sched.h>
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);

Thread-urile programate pentru o planificare cu politica SCHED_OTHER


pot avea doar prioritatea static 0, fiind considerate thread-uri de prioritatea
cea mai mic. Pentru celelalte dou politici de planificare, prioritile pot fi
cuprinse ntre 1 i 99. n Linux setarea pentru un thread a unei prioriti mai
mari dect 0 i prin urmare a unei politici de planificare alta dect
SCHED_OTHER este posibil doar pentru thread-urile cu privilegii de
administrator.

Funcii de stabilire a prioritii i politicii de planificare


Exist dou modaliti de stabilire a politicii de planificare i, respectiv a
prioritii unui thread. Prima modalitate este folosit n procesul de creare a
thread-ului i presupune folosirea unei structuri de atribute pthread_attr_t,
n cadrul creia trebuie stabilite anterior momentului crerii thread-ului
valorile dorite ale celor dou atribute. Cea de-a doua modalitate permite
schimbarea valorilor atributelor thread-ului n mod dinamic, de ctre el
nsui, pe durata execuiei sale. Pentru ambele modaliti sunt puse la
dispoziie funcii specifice, ce vor fi prezentate n cele ce urmeaz.
n cazul primei modaliti de lucru, funciile ce pot fi utilizate pentru
stabilirea, respectiv obinerea valorii curente a politicii de planificare i a
prioritii unui thread sunt cele prezentate mai jos, cu urmtoarea sintax:
#include <pthread.h>
#include <sched.h>
pthread_attr_setschedpolicy(
pthread_attr_t *attr, int policy);
pthread_attr_getschedpolicy(
pthread_attr_t *attr, int *policy);
pthread_attr_setschedparam(
pthread_attr_t *attr,
const struct sched_param *param);
pthread_attr_getschedparam(
pthread_attr_t *attr,
struct sched_param *param);

Semnificaia parametrilor funciei este urmtoarea:


attr
Reprezint structura de atribute ale unui thread, structur ce trebuie
iniializat anterior prin apelul funciei pthread_attr_init. Ea este apoi
transmis funciei pthread_create avnd ca efect crearea unui thread cu
atributele avnd valorile stabilite n cadrul respectivei structuri de
atribute.
policy
Reprezint, valoarea ce se stabilete pentru politica de planificare, n
cazul funciei pthread_attr_setschedpolicy, respectiv adresa variabilei
n care se obine valoarea curent a politicii de planificare, n cazul
funciei pthread_attr_getschedpolicy. Valoarea acestui parametru poate
fi una dintre constantele amintite mai sus: SCHED_OTHER,
SCHED_FIFO, SCHED_RR.
4

param
Este
o
structur
prin
care
se
specific
(funcia
pthread_attr_setschedparam) sau n care se obin (funcia
pthread_attr_getschedparam) parametrii politicii de planificare. n
forma actual, structura conine un singur cmp, care este prioritatea
thread-ului, aa cum este ilustrat i mai jos.
struct sched_param{
int sched_priority;
};

Setarea dinamic a celor dou atribute legate de planificarea thread-urilor se


poate face cu ajutorul funciei pthread_setschedparam cu urmtoarea
sintax:
#include <pthread.h>
int pthread_setschedparam(
pthread_t th, int policy,
const struct sched_param *param);

Funcia de obinere a valorilor acestor atribute este descris mai jos:


#include <pthread.h>
int pthread_getschedparam(
pthread_t th, int *policy,
struct sched_param *param);

n urma apelului funciei pthread_setschedparam thread-ul pentru care se


stabilesc noile valori ale parametrilor legai de planificare este mutat la
nceputul listei asociate noii prioriti a thread-ului i el poate ntrerupe
thread-ul curent, n caz c are prioritatea mai mare dect acesta.
Thread-ul curent poate ceda la un moment dat procesorul prin apelul
funciei pthread_yield, caz n care ele este pus la sfritul listei
corespunztoare prioritii statice a thread-ului i thread-ul din capul listei
respective va fi ales pentru execuie. n cazul n care thread-ul care apeleaz
funcia sched_yield este singurul cu acea prioritate, el i va continua
execuia. Sintaxa funciei sched_yield este urmtoarea:
#include <sched.h>
int sched_yield();

Avnd n vedere c implementarea pachetului PTHREADS sub Linux se


bazeaz pe utilizarea proceselor (lightweight processes), considerm util

precizarea ctorva funcii referitoare la planificarea proceselor, caz n care


chestiunile descrise mai sus rmn n totalitate valabile, dar raportate la
procese.
#include <sched.h>
int sched_setscheduler(
pid_t pid, int policy,
const struct sched_param *p);
int sched_getscheduler(pid_t pid);
int sched_setparam(
pid_t pid, const struct sched_param *p);
int sched_getparam(
pid_t pid, struct sched_param *p);

n cazul politicii de planificare SCHED_OTHER, calculul prioritii


dinamice a thread-urilor din aceast categorie poate fi influenat prin
stabilirea unei prioriti de baz a thread-ului. Stabilirea unei astfel de
prioriti i obinerea valorii ei curente se poate face cu ajutorul
urmtoarelor funcii:
#include <sys/time.h>
#include <sys/resource.h>
int getpriority(int which, int who);
int setpriority(int which, int who, int prio);

Semnificaia i valorile parametrilor funciilor este:


which
Indic entitatea pentru care se dorete setarea sau obinerea valorii
prioritii dinamice de baz. Valorile posibile sunt PRIO_PROCESS,
PRIO_PGRP, or PRIO_USER pentru cazul unui proces, grup de
procese i, respectiv utilizator.
who
Este interpretat n funcie de valoarea parametrului which i poate fi
identificatorul unui proces, al unui grup de procese sau al unui
utilizator. Valoarea 0 indic o raportare la procesul din care se
apeleaz funcia.
prio
Este valoarea prioritii dinamice de baz i poate avea o valoarea
cuprins ntre 20 i +20. Valorile mai mici indic o prioritate mai
mare. Stabilirea unei valori mai mici dect cea curent (adic,
creterea prioritii) poate fi fcut doar de ctre administratorul de
6

sistem. Valoarea implicit este 0. Funcia getpriority returneaz o


valoare cuprins ntre 1 i 40 (reprezentnd rezultatul expresiei
20-prio), deoarece valorile negative sunt rezervate de obicei
cazurilor de eroare.
Modificarea valorii prioritii dinamice de baz poate fi fcut cu ajutorul
funciei nice descris mai jos.
#include <unistd.h>
int nice(int inc);

Funcia are ca efect adunarea valorii inc la valoarea curent a prioritii


dinamice de baz a procesului. Valori negative, avnd ca efect creterea
prioritii procesului (thread-ului) pot fi specificate doar de ctre
administratorul de sistem, ceea ce nseamn c un proces obinuit nu poate
fi dect politicos (nice) n sensul scderii propriei prioriti n favoarea
altor procese. Prin urmare, funcia nu prezint un grad mare de utilitate.

3. Domeniul de planificare i domeniul de alocare


Domeniul de planificare a thread-urilor determin mulimea thread-urilor
care concureaz la un moment dat pentru obinerea unui procesor din cele
disponibile. Se definesc dou posibiliti de specificare a unui asemenea
domeniu, i anume:
domeniu de proces: cnd un thread concureaz pentru obinerea unui
procesor doar cu thread-uri aparinnd aceluiai proces;
domeniu sistem: cnd thread-urile tuturor proceselor din sistem sunt
luate n considerarea n momentul alocrii unui procesor unui thread.
O alt problem care se pune n cazul sistemelor multiprocesor este aceea a
determinrii setului de procesoare pe care un thread poate fi executat. Acest
set se numete domeniu de alocare. n cazul cel mai simplu, toate
procesoarele din sistem pot fi incluse n acelai domeniu de alocare, avnd
ca efect executarea tuturor thread-urilor din sistem pe oricare dintre ele. n
cazurile mai speciale, din motive de eficien sau atunci cnd anumite
thread-uri necesit un regim preferenial, se pot defini mai multe domenii de
alocare fiecare domeniu fiind destinat execuiei unui anumit grup de threaduri.

Dezavantajul utilizrii unui domeniu de proces pentru planificarea threadurilor este acela c la un moment dat thread-uri ale unui proces pot s
atepte dup eliberarea unui procesor, chiar n situaiile n care thread-uri cu
prioritate mai mic ale altor procese sunt n execuie pe unele procesoare.
Aceasta deoarece dintre thread-urile respectivului proces doar unul este ales
la un moment dat pentru execuie. n cazul domeniul sistem, thread-urile
aceluiai proces pot fi simultan n execuie, att datorit disponibilitii unor
procesoare, ct i datorit faptului c au prioritate mai mare fa de threadurile altor procese.
Funciile de mai jos pot fi folosite pentru stabilirea i obinerea valorii
atributului care determin pentru un thread domeniul de planificare.
#include <pthread.h>
pthread_attr_setscope(
pthread_attr_t *attr, int scope);
pthread_attr_getscope(
pthread_attr_t *attr, int *scope);

Valorile permise pentru atributul scope sunt:


PTHREAD_SCOPE_SYSTEM
PTHREAD_SCOPE_PROCESS

4. Proprietatea de motenire
Exist un atribut care ofer posibilitatea ca un thread s moteneasc
atributele de planificare ale thread-ului care l-a creat. Funcia prin care se
seteaz acest atribut se numete pthread_attr_setinheritsched i are
urmtoarea sintax:
#include <pthread.h>
pthread_attr_setinheritsched(
pthread_attr_t *attr, int inherit);

Parametrul inherit poate lua urmtoarele dou valori predefinite:


PTHREAD_EXPLICIT_SCHED: pentru cazul n care valorile
atributelor ce identific politica i prioritatea de planificare nu se
motenesc ci trebuie specificate explicit. Evident, dac nu sunt
specificate ele iau valorile implicite setate de ctre sistem, dar nu le
motenesc pe cele ale thread-ului creator.

PTHREAD_INHERIT_SCHED: pentru cazul n care politica i


prioritatea de planificare se motenesc de la thread-ul creator.

Funcia prin care se obine valoarea acestui parametru are urmtoarea


sintax:
#include <pthread.h>
pthread_attr_getinheritsched(
pthread_attr_t *attr, int *inherit);

5. Exemplu de utilizare
Codul de mai jos ofer un model de utilizare i testare a principalelor funcii
descrise mai sus pentru stabilirea politicii de planificare i a prioritii unui
thread.
#include <sched.h>
#include <stdlib.h>
void* fcTh(void* arg)
{
int i, policy;
struct sched_param schdPar;
int id = *(int*)arg;
sleep(2);
for (i=1; i<10000; i++) {
pthread_getschedparam(pthread_self(),
&policy, &schdPar);
printf("Thread %d has priority %d\n",
id, schdPar.sched_priority);
printf("Thread %d has policy: ", id);
switch (policy){
case SCHED_OTHER:
printf("SCHED_OTHER\n");
break;
case SCHED_FIFO:
printf("SCHED_FIFO\n");
break;
case SCHED_RR:
printf("SCHED_RR\n");
break;
} // end switch
} // end for
} // end fcTh()

main()
{
pthread_t th1, th2, th3;
pthread_attr_t attr1, attr2, attr3;
struct sched_param schdPar1, schdPar2, schdPar3;
int id1, id2, id3;
printf("The SCHED_FIFO min priority is: %d\n",
sched_get_priority_min(SCHED_FIFO));
printf("The SCHED_FIFO max priority is: %d\n",
sched_get_priority_max(SCHED_FIFO));
printf("The SCHED_RR min priority is: %d\n",
sched_get_priority_min(SCHED_RR));
printf("The SCHED_RR max priority is: %d\n",
sched_get_priority_max(SCHED_RR));
printf("The SCHED_OTHER min priority is: %d\n",
sched_get_priority_min(SCHED_OTHER));
printf("The SCHED_OTHER max priority is: %d\n",
sched_get_priority_max(SCHED_OTHER));
id1 = 1;
pthread_attr_init(&attr1);
pthread_attr_setschedpolicy(&attr1, SCHED_RR);
schdPar1.sched_priority = 10;
pthread_attr_setschedparam(&attr1, &schdPar1);
pthread_create(&th1, &attr1, fcTh, &id1);
id2 = 2;
pthread_attr_init(&attr2);
pthread_attr_setschedpolicy(&attr2, SCHED_RR);
schdPar2.sched_priority = 10;
pthread_attr_setschedparam(&attr2, &schdPar2);
pthread_create(&th2, &attr2, fcTh, &id2);
id3 = 3;
pthread_attr_init(&attr3);
pthread_attr_setschedpolicy(&attr3, SCHED_RR);
schdPar3.sched_priority = 12;
pthread_attr_setschedparam(&attr3, &schdPar3);
pthread_create(&th3, &attr3, fcTh, &id3);
pthread_join(th1, NULL);
pthread_join(th2, NULL);
pthread_join(th3, NULL);
}

10

6. Probleme
1. S se testeze funciile descrise n cadrul lucrrii. Se va folosi ca model
programul C de mai sus. S se stabileasc alternativ diferite politici de
planificare i prioriti ale celor trei thread-uri.
2. n cadrul problemei productor-consumator se cere introducerea unui nou
thread numit garbage-collector care are rolul de eliberare a spaiilor din
buffer coninnd mesaje preluate, dar neeliminate de ctre thread-urile
consumator. Se presupune c thread-urile productor i consumator sunt
programate pentru planificare utiliznd politica SCHED_RR, iar pentru
thread-ul garbage_collector se folosete politica SCHED_OTHER.
3. Se consider un pod pe care se poate circula doar ntr-un singur sens la
un moment dat. n plus, pe pod se pot afla simultan doar MAX_MASINI
maini. O main este reprezentat de un thread, care va executa
procedura Circula(), avnd urmtoarea form:
Circula (int directie)
{
IntraPePod(directie);
TraverseazaPod(directie);
IeseDePePod(directie);
}

(a) Se cere s se implementeze procedurile de mai sus,


folosind lacte i variabile condiionale, astfel nct s
fie respectate regulile de traversare a podului amintite
mai sus.
(b) Datorit faptului c respectarea regulilor de mai sus
poate duce la apariia cazului cnd mainile dintr-o
anumit parte a podului pot s atepte un timp
nedeterminat, atunci cnd din sens contrar vin
ncontinuu maini, se cere introducerea unui thread cu
rol de control a circulaiei (ceea ce n realitate este
realizat cu dou semafoare la fiecare intrare pe pod,
care indic pe rnd culoarea verde). Acest thread va
aloca un interval de traversare pentru fiecare direcie,
la expirarea acestui timp, schimbnd sensul de
circulaie. Opional se poate introduce un anumit grad
de inteligen controlorului de trafic, care s in cont
de fluxul de maini din ambele direcii. Acest
11

controlor inteligent va acorda un timp de traversare


mai mare pentru direcia din care vin mai multe
maini, sau dac dintr-o direcie nu vin maini, atunci
nu va schimba sensul de circulaie.
(c) S se introduc thread-uri care s joace rolul
mainilor de poliie sau salvare, adic vor avea o
prioritate mai mare dect a thread-urilor reprezentnd
maini obinuite. Acestea nu vor fi afectate de
direcia de circulaie impus de controlorul de trafic,
dar evident vor trebui s in cont de regulile enunate
iniial, adic s atepte dup mainile din sens contrar
care sunt pe pod i s nu se depeasc numrul
maxim de maini de pe pod.
4. Inversarea prioritilor. S se testeze funcionarea unui
proces cu trei thread-uri t1, t2 i t3, avnd fiecare trei
prioriti diferite 0<p1<p2<p3, n urmtorul context:
primul thread blocheaz un lact L pentru a modifica o
variabil V. ntre timp pornete cel de-al doilea thread
care va executa o bucl infinit, fr a ncerca ns
blocarea lactului L. Cel de-al treilea thread va ncerca
ulterior i el blocarea lactului. S se urmreasc i s se
comenteze rezultatul execuiei celor trei thread-uri.

12