Sunteți pe pagina 1din 7

Cap.4.

Interacţinea taskurilor concurente

4.1. Consideraţii generale


Exploatarea avantajelor aduse de considerarea unei aplicaţii ca fiind compusă din mai multe
taskuri ce se execută în paralel sau concurent impune, aşa cum a rezultat şi din exemplele
prezentate în capitolul anterior, folosirea unor mecanisme care să asigure interacţiunea
corectă a taskurilor pentru ca programul în ansamblu să aibă evoluţia dorită. Dening P. (în
Operating Systems Theory) a arătat că interacţiunea taskurilor poate fi redusă la trei tipuri de
operaţii multitasking:
-comunicarea între taskuri,
-sincronizarea taskurilor
-excluderea mutuală a taskurilor.
Comunicarea între taskuri este operaţia multitasking prin care se permite taskurilor să
schimbe date între ele. Datele comune (utilizate de mai multe taskuri) sunt memorate în memoria
internă a calculatorului în anumite zone declarate ca zone comune (pentru datele comune) şi la care
vor avea acces toate taskurile. Deoarece zonele de date comune organizate în memoria internă au o
capacitate mică şi limitează la un volum mic datele comune, în cazul unui volum mare de
date, acestea se organizează în fişiere plasate pe un suport de memorie externă (de obicei disc
magnetic). Majoritatea sistemelor de operare actuale oferă un sistem de lucru cu fişierele prin
intermediul unui SGF (sistem de gestiune a fişierelor). Dezavantajul acestei metode este legat de
timpii relativ mari pentru accesarea datelor din fişiere, dar metoda este utilizată datorită capacităţii
mari de memorare a acestor sisteme.
Sincronizarea este operaţia multitasking care asigură eşalonarea corectă a
execuţiei taskurilor în timp sau, altfel spus, care stabileşte o relaţie de ordine între instrucţiunile
executate de acestea, independent de vitezele relative ale lor.
Scopul unei metode de sincronizare este de a oferi unui task Ti aflat în execuţie mijloacele
care să-i permită, în primul rând, să blocheze un alt task activ Tj sau să se blocheze el însuşi în
aşteptarea producerii unui eveniment extern sau expirării unui interval de timp şi, în al doilea rând,
să deblocheze un task Tk căruia să-i transmită eventual şi unele informaţii. În evoluţia lor, două sau
mai multe taskuri se pot sincroniza fie în funcţie de o condiţie de timp, fie în funcţie de
un eveniment exterior. Mai exact, în cadrul unei aplicaţii, se poate întâlni situaţia ca un task Tk
pentru a-şi putea continua execuţia să fie nevoit să aştepte trecerea unui anumit interval de
timp sau realizarea unui eveniment din proces, eveniment controlat de regulă de un alt task
Ti. În cazul sincronizării pe o condiţie de timp, taskul Tk se va bloca până la expirarea
intervalului de timp menţionat după care va trece în starea READY şi îşi va putea continua
execuţia. În cazul sincronizării pe un eveniment exterior, taskul Tk se va bloca dacă evenimentul
nu a avut loc. De regulă, acest eveniment este supravegheat de un alt task Ti care va debloca taskul
Tk în momentul în care evenimentul s-a îndeplinit.
Operaţiile de sincronizare pot fi:
- explicite (directe), caz în care un task acţionează direct asupra altui task, primitivele de blocare şi
deblocare trebuind să fie astfel concepute încât să accepte ca parametru numele taskului asupra
căruia se acţionează;
- implicite (indirecte), caz în care un task acţionează asupra altui task prin declanşarea unor
mecanisme intermediare; primitivele de blocare şi deblocare nu mai folosesc explicit numele
taskului asupra căruia se acţionează, ci folosesc o serie de variabile special-create în acest
scop(semafoare, variabile eveniment etc.).
Restricţiile care determină sincronizarea taskurilor pot fi concentrate sub forma:
- operaţia A nu se poate executa înaintea trecerii intervalului de timp ∆T ;

28
- operaţia A nu se poate executa înaintea operaţiei B.
Excluderea mutuală este operaţia multitasking prin care se exclude accesul simultan al mai
multor taskuri la una şi aceeaşi resursă (zona de date din memorie, fişier, periferic etc.). Restricţiile,
în acest caz, sunt de tipul: operaţiile A, B, C, ..., X nu pot avea loc simultan.
Definiţie: Secvenţa de program a fiecărui task în care se apelează o resursă comună
partajată, se numeşte secţiune critică a taskului. (Din această cauză, de multe ori,
excluderea mutuală a taskurilor se mai întâlneşte şi sub denumirea de problema secţiunii critice).
Plecând de la ipoteza că intervalele de timp de execuţie ale taskurilor sunt diferite şi
necunoscute şi că orice task iese din secţiunea sa critică după un interval finit de timp, soluţia unei
probleme de acest tip trebuie să satisfacă următoarele condiţii:
a) - utilizarea exclusivă: la un moment dat un singur task şi numai unul se poate afla în
secţiunea sa critică corespunzătoare unei resurse;
b) - evitarea blocajului reciproc: dacă mai multe taskuri sunt blocate în aşteptarea aceleiaşi resurse
critice şi aceasta nu este ocupată, atunci unul dintre aceste taskuri (de regulă cel mai
prioritar), trebuie să poată să intre în secţiunea sa critică la capătul unui interval finit de timp;
c) - evitarea dependenţelor inutile: dacă un task este blocat în afara secţiunii sale critice,
acest blocaj nu trebuie să împiedice intrarea unui alt task în secţiunea sa critică.
Datorită condiţiilor restrictive similare, problemele sincronizării şi excluderii mutuale pot fi
tratate cu procedee comune. În prezent există o mare varietate de procedee puse la
dispoziţia utilizatorului pentru rezolvarea acestor operaţii multitasking şi anume:
- folosirea unor variabile de tip întreg numite semafoare (Dijkstra - 1965);
- folosirea unor variabile logice numite variabile eveniment (Dijkstra - 1965, Latteux -1980);
- cu ajutorul unor structuri de date numite mesaje şi a unor zone de memorie unde se depun aceste
mesaje numite celule mesaj sau cutii poştale (Hoare - 1971);
- cu ajutorul conceptului de monitor (Brinch Hansen - 1972);
- cu ajutorul conceptului de rendezvous (Ichbiach - 1979).
Observaţie: Ultimele trei procedee au avantajul de a rezolva în mod unitar şi problema comunicării
între taskuri.

4.2. Utilizarea semafoarelor pentru excluderea mutuală a taskurilor şi pentru sincronizarea lor
indirectă 10
Un semafor SEM este definit de perechea [v(SEM), q(SEM)], unde:
- v(SEM) = valoarea semaforului, care poate fi un număr întreg;
- q(SEM) = o coadă de aşteptare destinată să primească taskurile care eşuează în încercarea lor de a
trece de acest semafor.
Variabila v(SEM) este de tip întreg, iar valoarea sa iniţială v0(SEM) este fixată în momentul
creării semaforului.
Administrarea cozii q(SEM) este la latitudinea sistemului de operare, iar conţinutul
său înaintea primei utilizări a semaforului este vid.
Semafoarele pot fi împărţite în două categorii:
- semafoare care pot lua numai două valori, de obicei 0 şi 1, numite din acest motiv
semafoare binare;
- semafoare care pot lua orice valoare întreagă, numite semafoare generale sau contorizate.
In scopul manipularii semafoarelor s-au creat două primitive speciale, neîntreruptibile,
notate cu P şi V (notaţii introduse de Dijkstra) implementate în software-ul EXECUTIVULUI.
Primitiva P apare ca o cerere de depăşire a semaforului, iar primitiva V are semnificaţia eliberării,
în anumite condiţii, a unei autorizaţii de trecere.

29
Observaţie: Menţionăm că, semafoarele binare sunt suficiente pentru rezolvarea
operaţiilor multitasking menţionate, astfel încât, în continuare se vor face referiri numai la acestea.
Pentru semafoarele binare, primitivele P şi V au următoarea structură:
Primitiva P(SEM):
dacă v(SEM) = 0 atunci
q(SEM) ← Ti /* Blochează taskul Ti, adică taskul care a lansat primitiva,*/
/* introducându-l în coada de aşteptare a semaforului q(SEM) */
altfel
v(SEM) = v(SEM) - 1

Primitiva V(SEM):
dacă v(SEM) ≠ 0 atunci
dacă q(SEM) ≠ 0 atunci
Tj ← q(SEM) /* Deblochează taskul Tj, care poate fi fie primul task */
/* din coada de aşteptare a semaforului, fie taskul cel */
/* mai prioritar din aceasta */
altfel
v(SEM) = v(SEM) + 1
dacă q(SEM) ≠ 0 atunci
Tk ← q(SEM) /* Deblochează toate taskurile Tk din coada */
/* de aşteptare q(SEM) a semaforului SEM */

Prin utilizarea semafoarelor binare şi a primitivelor P şi V care acţionează asupra lor


aşa cum s-a arătat mai sus, se poate rezolva eficient problema excluderii mutuale a resurselor critice
partajate. Pentru aceasta fiecărei resurse critice i se asociază câte un semafor având iniţial valoarea
1. Pentru claritate considerăm:
Exemplul 4.1. Considerăm n taskuri T1,T2, ..., Tn care în execuţia lor pot apela simultan o aceeaşi
resursă. Pentru realizarea excluderii mutuale asociem acestei resurse critice un semafor binar
SEM având valoarea iniţială v0(SEM) = 1. Înainte de a intra în propria secţiune critică, fiecare task
este obligat să execute primitiva P(SEM), iar după părăsirea secţiunii critice trebuie să execute
operaţia V(SEM). Astfel, secvenţa de program care încadrează secţiunea critică a fiecărui task arată
ca în Figura 4.1.
Task Ti
Intrare controlatá
P ( SEM ) ín secţiunea critică

Fig.4.1. Sectiune criticá

Ieşire controlată
V ( SEM ) din secţiunea critică

Taskul Ti aflat în execuţie care efectuează operaţia P(SEM) se blochează dacă v(SEM)=0
şi îşi continuă execuţia dacă v(SEM)=1. În cazul blocării taskului Ti pe operaţia P(SEM),
sistemul de operare va da controlul unui alt task Tj din coada de aşteptare q(SEM) a semaforului,
aflat în starea READY. Dacă taskul Ti intră în propria sa secţiune critică, atunci v(SEM) = 0 şi

30
nici o altă operaţie P(SEM) nu mai poate avea loc, deci nici un alt task Tj, j  i nu mai poate intra
în sectiunea critică a programului său, până când taskul Ti nu o părăseşte pe a lui,
moment în care executând primitiva V(SEM), face v(SEM) = 1. În acest moment are loc
deblocarea taskurilor care aşteptau să intre în propria lor secţiune critică pentru aceeaşi resursă.
Există şi situaţii nedorite şi care trebuie eliminate. De exemplu, dacă taskul aflat în execuţie
este blocat în propria sa secţiune critică, atunci întrucât v(SEM) a rămas 0 (după execuţia primitivei
P(SEM)) se realizează blocajul infinit al tuturor celorlalte taskuri care utilizau aceeaşi resursă. Din
această cauză trebuie luate măsuri pentru eliminarea acestei situaţii, sau de eliberare a resursei.
Pentru a-i garanta fiecărui task introducerea în secţiunea sa critică după un interval de timp
finit, trebuie acordată o atenţie deosebită administrării cozilor de aşteptare a semafoarelor. Cea mai
simplă metodă din acest punct de vedere este metoda FIFO, dar, în multe situaţii, metoda
după priorităţi (utilizată în multe EXECUTIVE de timp real) poate prezenta avantaje evidente.
Semafoarele binare împreună cu primitivele P şi V pot constitui un mijloc de soluţionare a
problemelor de sincronizare indirectă a taskurilor cu evenimente exterioare. Primitiva P acţionează
ca o operaţie de blocare a unui task, în timp ce primitiva V poate fi privită ca un semnal
de deblocare al acestuia. Valoarea semaforului v(SEM) se asociază cu starea evenimentului exterior
astfel:
v(SEM) = 0 - evenimentul nu a avut loc;
v(SEM) = 1 - evenimentul a avut loc.
În aceste condiţii, valoarea iniţială a semaforului va fi totdeauna zero (v0(SEM)=0).
În cazul a două sau mai multe taskuri sincronizate pe un singur eveniment exterior,
procedeul este foarte eficient şi se desfăşoară ca în exemplul 4.2. 10
Exemplul 4.2. Considerăm 2 taskuri T1 şi T2 care trebuie sincronizate pe un eveniment
exterior, eveniment de îndeplinirea căruia este condiţionată la un moment dat execuţia taskului T1.
Pentru aceasta procedăm astfel:
Asociem acestui eveniment un semafor SEM. Valoarea semaforului v(SEM) se asociază
cu starea evenimentului exterior aşa cum s-a arătat mai sus. Realizarea evenimentului respectiv este
supravegheat de taskul T2. Atât timp cât evenimentul nu a avut loc, semaforul SEM are valoarea
v(SEM)=0. Taskul T1 fiind în execuţie şi ajungând la momentul critic al programului, va încerca să
execute operaţia P(SEM) şi se va bloca deoarece v(SEM)=0. Taskul T2 va debloca taskul T1
executând primitiva V(SEM) care va face v(SEM)=1 când va sesiza îndeplinirea evenimentului.
Structura programelor celor două taskuri în vederea sincronizării pe un eveniment exterior este
prezentată în Fig.4.2.
Task T1 Task T2
Secvenţă de program Secvenţă de program destinată
independentă de eveniment supravegherii evenimentului

P ( SEM ) V ( SEM ) Fig.4.2.

Secvenţă de program dependentă Secvenţă de program


de eveniment

Precizăm că acest procedeu devine ineficient în cazul unor taskuri sincronizate pe mai multe
evenimente externe. De asemenea, prin intermediul acestor variabile (semafoare binare), nu este
posibilă realizarea sincronizării taskurilor pe o condiţie de timp. Semafoarele rămân însă foarte
eficiente în rezolvarea problemei secţiunii critice.

31
11
4.3. Utilizarea variabilelor eveniment pentru sincronizarea indirectă şi excluderea mutuală
a taskurilor
Variabilele tip eveniment sunt variabile logice speciale, partajate care pot fi manipulate
numai cu ajutorul unor primitive special-compuse în acest scop. Aceste variabile pot fi declarate
prin program sau pot fi parte constituentă a EXECUTIVULUI, caz în care acestea se cunosc şi pot
fi numai apelate prin program. Operaţiile posibile asociate unei variabile eveniment sunt, de
regulă, DECLANŞAREA provocată de un task supraveghetor şi AŞTEPTAREA şi/sau
CONSUMAREA (sau ambele), executate de un task a cărei execuţie este condiţionată de
realizarea respectivului eveniment. Efectul acestor operaţii depinde de natura evenimentului. În
acest sens se definesc evenimente nememorate şi evenimente memorate. În cazul evenimentelor
nememorate o condiţie esenţială pentru ca acestea să fie exploatate constă în aceea că sosirea lor
să fie aşteptată. Astfel, dacă unul sau mai multe taskuri a căror execuţie depinde de un astfel
de eveniment se află, în momentul sosirii evenimentului, în starea BLOCKED, ele vor fi
trecute imediat în starea READY. Altfel, declanşarea evenimentului rămâne fără efect şi, în plus,
el este pierdut. Justificarea unui astfel de mod de tratare se explică prin faptul că în anumite
aplicaţii, anumite evenimente sunt semnificative numai în anumite momente ale derulării procesului
şi, în cosecinţă, dacă nu sunt aşteptate acestea nu mai prezintă nici un interes.
Evenimentul memorat este obţinut prin extensia celui nememorat şi este reprezentat
printr-o variabilă logică notată "EV" care poate lua valorile FALS (asociat cu valoarea 0)
sau ADEVĂRAT (asociat cu valoarea 1), indicând cele două alternative posibile: nedeclanşarea,
respectiv declanşarea acestui eveniment.
Când un task ajunge într-un punct în care continuarea execuţiei lui este condiţionată
de sosirea unui eveniment asociat cu variabila EV, el se va bloca numai dacă evenimentul aşteptat
nu s-a produs (EV = FALS). Dupa ce s-a blocat sosirea evenimentului (EV = ADEVĂRAT)
provoacă trecerea taskului care îl aştepta în starea READY. Acum este necesară exprimarea faptului
că respectivul eveniment a fost consumat. Consumarea sau anularea unui eveniment se face prin
atribuirea variabilei EV a valorii FALS.
De regulă, în aplicaţiile de conducere a proceselor se operează cu conceptul de eveniment
memorat, primitivele care pot acţiona asupra variabilelor eveniment asociate fiind:
AŞTEAPTĂ(expresie logică formată cu variabile eveniment): blochează taskul până când
expresia logică conţinând una sau mai multe variabile eveniment capătă valoarea ADEVĂRAT.
DECLANŞEAZĂ(EV): face ca variabila eveniment EV să capete valoarea ADEVĂRAT şi
are semnificaţia că evenimentul asociat cu această variabilă logică a avut loc.
CONSUMĂ(EV): face ca variabila eveniment menţionată EV să capete valoarea FALS şi
are semnificaţia că evenimentul asociat cu această variabilă logică s-a consumat.
Valoarea iniţială a unei variabile eveniment este FALS. Variabilele eveniment sunt diferite
de semafoare şi prin aceea că primitiva AŞTEAPTĂ(⋅ ) nu le modifică valoarea.
Variabilele tip eveniment sunt adecvate pentru sincronizarea taskurilor în funcţie de
evenimente.
Exemplul 4.3. Structura programelor a două taskuri T1 şi T2 sinctonizate pe un eveniment
exterior căruia i se asociază variabila eveniment EV este prezentată în Fig.4.3
Taskul T2 trebuie să conţină după directiva AŞTEAPTĂ(EV) directiva CONSUMĂ(EV) care
semnifică fapul că evenimentul a fost luat în considerare şi care va permite o nouă aşteptare din
partea taskului T2 pe acelaşi eveniment, în cazul funcţionării celor două taskuri în buclă
infinită. De asemenea, taskul T1 trebuie să aibă posibilitatea de a da controlul taskului T2 imediat
după îndeplinirea evenimentului. Aceasta se realizează fie prin introducerea după directiva

32
DECLANŞEAZĂ(EV) a unei directive care să declare eveniment semnificativ, fie prin construirea
directivei DECLANŞEAZĂ(EV) cu o opţiune de acest fel.
Taskul T1 Taskul T2

Secvenţă de program destinată Secvenţă de program


supravegherii evenimentului independentă de eveniment

DECLANŞEAZĂ(EV) Evenimentul
AŞTEAPTĂ(EV)
a avut loc

Secvenţă de program CONSUMĂ(EV)

Fig.4.3. Evenimentul a fost Secvenţă de program


luat în considerare dependentă de eveniment 11
Exemplul 4.4. Considerăm trei taskuri T1, T2, T3, primele două urmărind îndeplinirea a
două evenimente externe, asincrone, asociate cu variabilele EV1 şi EV2, iar al treilea fiind
condiţionat în execuţia lui, la un moment dat, de îndeplinirea ambelor evenimente. Ca atare,
la momentul critic, taskul T3 va fi obligat să execute primitiva AŞTEAPTĂ(EV1 şi EV2) şi
se va bloca dacă cel puţin una dintre variabile are valoarea FALS, ceea ce semnifică faptul că cel
puţin unul dintre evenimente nu a avut loc. Pe măsură ce evenimentele au loc, taskul T1, respectiv
T2 vor executa primitivele DECLANŞEAZĂ(EV1), respectiv DECLANŞEAZĂ(EV2) care vor
avea ca efect deblocarea taskului T3 şi aducerea lui în situaţia de a-şi putea continua
execuţia. Structura programelor celor trei taskuri este prezentată în Fig.4.4.

Task T1 Task T2 Task T3


Secvenţă de program Secvenþa de program Secvenţă de program
destinată supravegherii destinatá supravegherii independentă de
evenimentului EV1 evenimentului EV2 evenimente

DECLANŞEAZÁ(EV1) DECLANŞEAZÁ(EV2) AŞTEAPTÁ(EV1 şi EV2)

Sevenţă de program Secvenţă de program CONSUMĂ(EV1)


CONSUMĂ(EV2)

Fig.4.4. Secvenţă de program


dependentă de
evenimentele EV1 şi EV2

Variabilele tip eveniment pot fi utilizate şi în realizarea operaţiilor multitasking de


excludere mutuală. Pentru evitarea accesului simultan a două sau mai multe taskuri la o resursă
partajată, acesteia i se asociază o variabilă eveniment EVM căreia i se atribuie iniţial
valoarea ADEVĂRAT. Directiva DECLANŞEAZĂ(EVM) simulează primitiva V(de la

33
semafoare), iar ansamblul de directive AŞTEAPTĂ(EVM) urmată imediat de CONSUMĂ(EVM)
poate simula primitiva P, dacă secvenţa se execută neîntreruptibil, ceea ce nu este greu de realizat.
Structura a două taskuri T1 şi T2 în situaţia de excludere mutuală este prezentată în Fig.4.5.
Task T1 Task T2
Secvenţă de program Secvenţă de program

AŞTEAPTĂ(EVM) AŞTEAPTĂ(EVM)
CONSUMĂ(EVM) CONSUMĂ(EVM)
Fig.4.4.
Secţiune critică Secţiune critică

DECLANŞEAZĂ(EVM) DECLANŞEAZĂ(EVM)

Secvenţă de program Secvenţă de program

Primul task care intră în secţiunea critică ajunge în aceasta trecând de


primitiva AŞTEAPTĂ(EVM) - posibil, deoarece iniţial EVM=ADEVĂRAT, executând apoi
primitiva CONSUMĂ(EVM) ceea ce duce la EVM=FALS. În acest moment orice alt task care
doreşte să pătrundă în propria sa secţiune critică se va bloca pe primitiva AŞTEAPTĂ(EVM) până
când taskul iniţial abandonând resursa comună execută primitiva DECLANŞEAZĂ(EVM) în
urma căreia variabila eveniment EVM capătă valoarea ADEVĂRAT.

34