Sunteți pe pagina 1din 19

Curs 5-6 – Programare in timp real

Limbaje timp –real şi noţiuni de programare


concurentă
1. Generalităţi
Un program concurent este compus din mai multe părţi care sunt executate
concurent, adică există durate în care sunt mai multe activităţi executabile. Programele
concurente pot fi implementate folosind limbaje de programare care acceptă programarea
concurentă cum ar fi ADA, Modula-2, Occam şi Java, sau folosind un sistem de
operare care oferă facilităţi multitasking (de exemplu sistemul de operare UNIX, QNX,
programand in C).
Un proces al unui program concurent trebuie văzut ca o secvenţă de program care este
executată efectiv asincron şi prin urmare, nu se poate prezice;
 viteza relativă cu care este executat un proces;
 în care punct este suspendat un proces pentru execuţia prealabilă a altuia;
 alte temporizări ale proceselor.

Nedeterminismul referitor la temporizările relative ale proceselor face ca depanarea şi


testarea programelor concurente să fie mult mai dificile decât cele ale programelor
secvenţiale.
Programele concurente pot fi implementate pe sisteme cu:
 multiprogramare;
 multiprelucrare;
 prelucrare distribuită.
Cooperarea proceselor constă din:
 comunicare ~ schimbul de date de la un proces la altul;
 sincronizare - ordonarea secvenţelor în unele puncte din procese conform
specificaţiilor.

Cele mai importante probleme ridicate de execuţia concurentă a programelor sunt:

1. Execuţia concurentă pe un sistem monoprocesor care implică:


 comutarea proceselor- deci existenţa unui mecanism de comutare.
 Planificarea –adică să existe un algoritm care să decidă când să comute de la u
proces la altul şi care sp fie următorul proces de executat.

2. Excluderea mutuală care cere ca două părţi concurente ale unui program să nu fie
executate concurent. Aceste părţi se numesc secţiini critice, alt proces trebuie să fie
impiedicat să treacă la execuţia secţiunii sale critice până când primul nu iese din
execuţia secţiunii critice.
Se poate defini o clasă a secţiunii critice care constă dintr-un set de secţiuni critice
care trebuie să fie executate prin excludere mutuală cu alte secţiuni din aceeaşi clasă, fără
însă a fi necesară execuţia prin excludere mutuală cu alte secţiuni critice din alte clase.
3. Problema producător-consumator implică existenţa a două procese. Procesul
producător generează date pe care le transmite procesului consumator. Se cere ca:
 Articolele de date să fie recepţionate de către consumator în aceeaşi ordine în care
au fost transmise de către producător.
 Să nu piardă nici un articol de date prin transferul de la producător la consumator.
 Să nu fie inserate alte articole (sau duplicate ale unora existente) în timpul
transferului.

4. Problema cititori şi scriitor implică existenţa unui fişier de date în care un proces
scriitor depune date. Iar procesele cititoare le preiau de acolo. Se cere :
 Orice număr de procese cititoare să poată accesa simultan (fără excludere
mutuală) fişierul de date;
 Dacă un proces scriitor a început să depună date în fişier, nici unui task cititor nu
trebuie să i se permită accesul în fişier.

5. Problema cititori şi scriitori poate fi o generalizare a problemei precedente prin


existenţa mai multor taskuri scriitoare. Cerinţele de sincronizare contsau din :
 Orice număr de procese cititoare să poată accesa simultan fişierul de date;
 Numai un singur proces scriitor să poată accesa la un moment dat fişierul.
 Dacă un proces scriitor accesează fiierul, nici un proces cititor să nu-l acceseze în
aceea perioadă de timp.

6. Problema utilizării în comun a resurselor constă din excluderea mutuală a


proceselor de la utilizarea simultană a unor resurse.

Moduri de implementare a concurenţei

Programele concurente pot fi implementate folosind:


 Mecanisme hardware.
 Mecanisme software.
La rândul lor mecanismele software pot fi realizate în :
 Nucleul sistemului de operare.
 Mediul de execuţie a programului.
 Biblioteci realizate de către programatorul aplicaţiei
Cele mai cunoscute mecanisme hardware pentru implementarea concurenţei sunt :
 Instrucţiuni pentru blocarea şi deblocarea sistemului de întreuperi.
 Instrucţiuni pentru rezervarea şi renunţarea la accesul exclusiv la zone de
memorie.
 Instrucţiuni individuale executabile din procese diferite pentru incrementarea sau
decrementarea unei variabile comune.
 Transmiterea mesajelor în sistemele distribuite.
Rezervarea accesului la memorie se face în multe sisteme de calcul pe pagini sau pe zone
de memorie. În acest curs vor fi prezentate numai facilităţile de programare concurentă
oferite- de către cele mai cunoscute limbaje de programare.

Cerinţele unui limbaj de timp-real de nivel superior


Spre deosebire de limbajele de asamblare, nu toate limbajele de
programare de nivel superior pot fi utilizate eficient pentru implementarea unor
aplicaţii de timp-real.
Printre cele mai importante cerinţe care trebuie luate în considerare la alegerea
unui limbaj de programare pentru implementarea aplicaţiilor de timp-real sunt:
 să fie uşor de integrat cu metodele de proiectare utilizate în mod curent;
 să fie flexibile;
 programele să fie portabile;
 programele să fie uşor de înţeles pentru eventuale reactualizări sau corectări;
 să fie eficiente din punctul de vedere al vitezei de execuţie şt al cerinţelor de
memorie;
 să garanteze securitatea operaţiilor în timpul compilării;
 să garanteze securitatea în timpul execuţiei programelor;
 să permită utilizarea unor metode de proiectare şi compilare modulare;
 să permită abstractizarea datelor;
 să fie eficiente din punctul de vedere al structurilor de control al fluxurilor de
date;
 să ofere facilităţi pentru utilizarea resurselor la nivelul maşinii;
 să ofere facilităţi pentru tratarea excepţiilor;
 să permită şi să fie eficiente în tratarea întreruperilor;
 să permită interfaţarea cu proceduri scrise în limbaje de asamblare;
 să aibă posibilitatea de interfaţare cu alte limbaje de nivel înalt;
 să fie multitasking - cea mai importantă cerinţă.
Printre cele mai cunoscute limbaje utilizate pentru aplicaţiile de timp-real sunt:
 CORAL66 - acronimul din engleză de la Computer On-line Real-time Application
Language (apărut in anul 1966);
 Pascal (în varianta concurentă) - apărut in anul 1970
 RTL/2 - acronimul de la Real Time Language Two (dezvoltat în anul 1971);
 C- proiectat în 1972. (Posix)
 Forth - dezvoltai prin anii 1970;
 PL/M - acronimul de la Programming Language/Microcomputers dezvoltat în
1976;
 Ada – dezvoltat în 1970
 Modula-2 – dezvoltat între anii 1977 şi 1980;
 Java - apărut in anii 1995.

2. Regiuni critice
C A. R. Hoare a propus o construcţie (specifică limbajelor de programate de nivel înalt)
with v do Q,
prin care se simplifică utilizarea semafoarelor, v reprezintă o variabilă declarată comună
var v : shared T;

T este tipul variabilei v, iar Q reprezintă o secvenţă critică de instrucţiuni. In acest mod,
regiunile critice la care se referă variabila v se vor exclude reciproc în timp. Utilizând
această construcţie se evită necesitatea încadrării secţiunii critice cu primitivele P şl V,
sau wait şi set (dacă se lucrează cu variabile eveniment).
Regiunile critice pot fi realizate şi cu mecanismele din nucleele sistemelor de
operare multitasking. Implementarea regiunilor critice este simplă. Declararea variabilei
v ca variabilă comună implică, de fapt, construirea unui semafor, sau a unei variabile de
tip eveniment.
O dezvoltare a construcţiei de mai sus s-a materializat prin regiunile critice condiţionate:

with v then B do Q:

unde B este o expresie logică. Execuţia secvenţei critice Q este condiţionată, în plus faţă
de cazul precedent, de faptul că B trebuie să aibă valoarea logică. ■adevărat (true). Dacă
după intrarea în regiunea critică, această condiţie nu este îndeplinită, atunci tascul este
trecut în aşteptare până la îndeplinirea ei.
Şi această construcţie poate fi realizată cu primitivele nucleului unui system de
operare multitasking. Pentru implementarea acestei construcţii se propune utilizarea
primitivelor P şi V. Fie semv, semaforul pe care se aşteaptă obţinerea accesului în
regiunea critica şi queue B, coada de asteptare în cazul în care nu este îndeplinită condiţia
B. Secvenţa (prezentată în limbaj de descriere):

P(semv);
wait(B);
*secventa critica;
V(semv);
set(B)

nu satisface totdeauna cerinţele din cauză că este posibilă situaţia în care condiţia B nu
este îndeplinită datorită activităţilor altor taskuri. Aceste taskuri, pentru a modifică
valoarea expresiei logice B, trebuie la rândul lor să intre în regiuni critice controlate de
aceeaşi valoare comună v. În acest caz taskurile se blochează reciproc.
Un exemplu de astfel de situaţie este cel al două sau mai multor taskuri care
schimbă între ele mesajul prin intermediul unei cutii postale. Taskurile trebuie să se
excludă reciproc la depunere, sau preluare de mesaje. Fie semn semaforul care
controlează accesul la cutia poştală (utilizând P(semv)). Un task nu poate prelua mesajul
dacă nu îndeplineşte condiţia semhold.val > 0. Atunci el blochează accesul altui task,
care vrea să depună un mesaj.

O implementare a construcţiei de mai sus este secvenţa de forma:

repeat
P(semv);
if non B then
begin
V(semvv);
*secventa de relaxare;
endthen;
until B;
reset(B);
*secventa critica;
set(B);
V(semv);

Mai sus s-a implementat o structură care execută o secvenţa de relaxare pentru verificarea
accesului la variabila logică B, asupra căreia s-au definit operaţiile set şi reset. Aceasta
poate fi, de exemplu, o temporizare realizată prin intermediul ceasului.

7.3. Monitoare
În implementarea regiunilor critice condiţionate apare dificultatea realizării primitivei
wait(B), C. A. R. Hoare si Brinch Hansen au propus un alt concept derivand din regiunile
critice condiţionate, numit monitor, şi constând dintr-o structură de date împreună cu
procedurile de acces la ea. Taskurile pot citi şi modifica datele, dar numai un singur task
poate utilize procedurile (sau, o procedura a) unui monitor la un moment dat.
Folosirea de către utilizator a unui monitor presupune secvenţa de forma:

type nume_monitor(parametri) = monitor;


begin
var ………………
procedure entry nume_procedura 1(parametri);
var…………….
begin……….. end
………………..
procedure entry nume_procedura n(parametri)
var…………
begin…………..end
begin
*initializare date;
end
end {monitor}

sistemul de operare sau mediul de executie construieste cozi de aşteptare (vezi variabilele
de tip eveniment). Fiecare dintre proceduri va fi cuprinsă între primitivele wait şi set
astfel încât numai un task foloseşte la un moment dat o procedură.
Pentru exemplificare, se construieşte o cutie postală cu procedurile de acces la ea sub
forma unui monitor. Se defineşte tipul sequence of messages ca fiind o listă simplu
înlănţuită unde:

type message = record next: ^message;


content : array[1..N] of character
end
Se vor accepta asupra ei operaţiile effectuate cu liste: init, append, etc.

type cutie_postala = monitor;


begin
var buf: sequence of messages;
notempty, notfull: event;
end
const max = ……….;
procedure entry expediere (x : message);
begin
wait (notfull);
append (buf, x);
if k < max then set(notempty);
set {primire}
end {cutie_postală}

Dacă implementarea schemei de mai sus s-ar face astfel încât numai un singur task să
intre la un moment dat în monitor, atunci în ipoteza că trebuie să aştepte pentru a depune
un mesaj într-o cutie poştală plină. Soluţia de excludere mutuală de la utilizarea unei
singure proceduri evită anomalia menţionată.

7.7 Limbajul Java


Limbajul de programare Java este complet orientat pe obiecte si este neutru din
punct de vedere arhitectural. Java este un limbaj compilat si interpretat . Aceasta
inseamna ca dupa editarea unui program , codul sursa este compilat intr-un format de
nivel mediu ,numit cod de octeti . Acesta este destinat unei masini virtuale Java. Pentru
executia lui pe un calculator real , trebuie sa fie interpretat mai intai de un mediu de
executie Java . Aplicatiile Java pot fi executate pe sisteme monoprocesor , sisteme
multiprocesor sau sisteme distribuite .
In aceasta sectiune sunt prezentate doar conceptele din Java care privesc
implementarea aplicatiilor de timp-real . Pentru insusirea limbajului se recomanda
consultarea bibliografiei .
Din punct de vedere al limbajului Java un proces este un program care este executat
in propriul sau spatiu de adresa . Java este un sistem cu multiprelucrare , prin urmare ,
accepta mai multe procese care sunt rulate concurent in propriile lor spatii de adresa .Un
fir este o secventa de executie din cadrul unui proces . Deci un fir poate fi executat numai
in contextul unui proces .

Crearea si executia firelor


Un fir trebuie declarat , creat , startat , oprit si eventual distrus .
Crearea claselor care folosesc fir de executie se face prin extinderea clasei Thread
sau prin implementarea clasei Runnable .

Crearea firelor prin extinderea clasei Thread se face prin declaratii de forma
urmatoare :
public class NumeFir extends Thread {
public void run () {
………….
}
}

S-a definit un nou fir numit NumeFir care executa ceva la apelul metodei run() .
Metoda run() este asemenatoare ca functionalitate cu functia main() din limbajul C sau
C++ . Ea contine corpul principal al codului care va fi rulat de firul de executie.

Implementarea interfetei Runnable face ca clasele existente sa fie convertite in fire de


executie fara modificarea claselor de baza .Formatul utilizat in acest caz este :

public class NumeFir extends ImportClass


implements Runnable {
public void run() {
………….
}
}

Implementarea interfetei Runnable are ca efect realizarea unui fir de executie separat.
Firele trebuie instantiate in ambele cazuri .
Pentru crearea unei instante a clasei NumeFir trebuie adaugata declaratia :

NumeFir nume = new NumeFir();

Firele create trebuie lansate in executie . Aceasta se realizeaza pentru exemplul dat
cu :

nume.start();

Executia unui fir poate fi suspendata temporar cu metoda stop(), adica pentru
exemplul dat :

nume.stop();

Executia unui fir poate fi suspendata temporar cu metoda suspend() .


Pentru exemplul dat aceasta este :

nume.suspend();

sau poate fi reluata cu metoda resume() , adica :

nume.resume();
Metoda stop() nu distruge firele . Pentru a elimina complet un fir trebuie apelata
metoda destroy(). In Java nu este obligatorie distrugerea firelor. Sistemul Java de
colectare a gunoiului rezolva aceasta problema . Pentru a avea certitudinea ca au fost
sterse toate referintele la obiectul fir de executie de firul nume , se recomanda utilizarea
instructiunii :

nume = null;

Operatia forteaza sistemul pentru colectarea gunoiului sa programeze dezalocarea


resursei pentru obiectul nume .

Un exemplu de program care are pe langa firul principal de executie inca doua
(Thread-1 si Thread-2) este urmatorul :

class Testfire extends NumeFir {


public static void main (String args[ ] ) {
System.out.println(*** Aplicatie cu fire ***);
NumeFir nume1 = new NumeFir(); // instantiaza primul fir
NumeFir nume2 = new NumeFir(); // instantiaza al doilea fir
nume1.start(); // starteaza primul fir
nume2.start(); // starteaza al doilea fir
System.out.printl(Firele au fost startate);
try {(nume1.join()); } // asteapta terminarea primului fir
catch (InterruptedException ignored) {}
try {(nume2.join()); } // asteapta al doilea fir
catch (InterruptedException ignored) {}
nume1.stop(); // opreste executia primului fir
nume2.stop(); // opreste executia celui de al doilea fir
nume1 = null; // elibereaza legaturile primului fir
nume2 = null; // elibereaza legaturile pentru al doilea fir
System.out.println(Terminat executie fire);
}
}

public class NumeFir extends Thread {


public void run() {
System.out.println(Acesta este firul :  + this.getName() );
}
}

Metoda getName() a fost utilizata cu scopul de a obtine numele firului care o


solicita .
Programul de mai sus va scrie pe ecran :

***Aplicatie cu fire***
Firele au fost startate
Acesta este firul : Thread-1
Acesta este firul : Thread-2

Terminat executie fire


Application Exit …

Instructiunea try este folosita cu scopul de a informa interpretorul Java ca un bloc de


date poate genera o exceptie si ca tratarea ei se va face in instructiunea imediat urmatoare
. Instructiunea sau blocul (grupul) de instructiuni ce urmeaza dupa try corespunde codului
care poate genera o exceptie . Dupa instructiune sau blocul respectiv urmeaza codul care
trateaza exceptia .
Ca exemplu de utilizare a interfetei Runnable se prezinta o aplicatie in care functia
main creeaza 3 fire de tip a si 3 fire de tip b , care se intretes doua cate doua .

public class TestRunnable {


public static void main (String argv[ ]) {
int i;
InterfataRunnable_a ira = new InterfataRunnable_a();
InterfataRunnable_b irb = new InterfataRunnable_b();
for (i=0; i<3; i++) {
Thread ta = new Thread(ira); // main creeaza un nou fir
System.out.println(Noul fir este creat de :  +
Thread.currentThread().getName() );
Thread tb = new Thread(irb); // main creeaza un nou fir
ta.start();
tb.start();
try {tb.join(); } // asteapta terminarea firului creat
catch (InterruptedException ignored) {}
ta.resume(); // reactiveaza firul de tip a
try {ta.join(); } // asteapta terminarea firului creat
catch (InterruptedException ignored) {}
}
}
}

public class InterfataRunnable_a inplements Runnable {


public void run() {
System.out.println(Fir de tip a cu indicatorul :  +
Thread.currentThread().getName() );
Thread.currentThread().suspend(); // suspenda firul de tip a
System.out.println(Firul  + Thread.currentThread().getName() + isi incheie
activitatea);

}
}
public class InterfataRunnable_b inplements Runnable {
public void run() {
System.out.println(Fir de tip b cu indicatorul :  +
Thread.currentThread().getName() );
}
}

Programul va scrie pe ecran :

Noul fir creat de: main


Fir de tip a cu indicatorul: Thread-1
Fir de tip b cu indicatorul: Thread-2
Firul Thread-1 isi incheie activitatea
Noul fir creat de: main
Fir de tip a cu indicatorul: Thread-3
Fir de tip b cu indicatorul: Thread-4
Firul Thread-3 isi incheie activitatea
…………………………………….

Sincronizarea firelor

Un obiect sau o metoda pot fi accesate de mai multe fire de executie . Pentru a
realiza excluderea mutuala de la executarea unei sectiuni critice se poate folosi cuvantul
cheie synchronized().Ca exemplu de sincronizare este :
public void numeIndex() {
synchronized (index) {
index++;
……….
}
}
Metoda numeIndex contine un bloc sincronizat . Obiectul index este blocat de un fir
care a inceput utilizarea metodei pana la terminarea executiei blocului respectiv .

Tratarea exceptiilor

Dupa cum s-a vazut si din exemplele anterioare , limbajul Java ca de altfel si
limbajul Ada ofera posibilitati de tratare a exceptiilor . Exceptiile sunt erori remediabile .
Tratarea exceptiilor face programele mai robuste si mai performante. Aceasta este o
cerinta foarte importanta pentru aplicatiile de timp-real .
In Java , exceptiile sunt considerate o clasa de obiecte care pot trata diferite
probleme aparute in executie .
Exista trei moduri pentru tratare a exceptiilor :

 ignorarea lor ;
 prelucrarea lor de catre codul in care apar ;
 semnalizarea lor codului care a apelat metoda ce a generat exceptia pentru a
fi prelucrata de catre aceasta .

Exceptiile care nu sunt tratate explicit de catre program sunt transmise interpretorului
Java care le trateaza sau opreste executia programului .
Exista mai multe metode oferite de limbajul Java pentru tratarea exceptiilor :

1. catch care dupa cum spune si numele ei (interceptare) cuprinde codul care
detecteaza aparitia unei exceptii si o trateaza in mod corespunzator.
2. try specifica faptul ca programul va incerca sa execute un bloc de cod care
genera o exceptie .
3. finally precizeaza actiunile care urmeaza a fi executate in cazul in care nici una
dintre instructiunile catch anterioare nu a rezolvat problema .

Planificarea firelor

Exista mai multe moduri de executie a firelor unui program scris in Java . Unul este
prin divizarea timpului ( engl. : timeslicing ) si consta din alocarea periodica a unor mici
cuante de timp fiecarui fir .Celalalt mod implica planificarea firelor . Variante mai vechi
ofereau numai planificarea nepreemtiva . Tendinta de evolutie a mediilor de excutie Java
este de includere a facilitatilor necesare unor aplicatii de timp-real . Acestea ar trebui , in
primul rand , sa poata specifica explicit obiecte rezidente permanent in memoria interna
( operativa ) a sistemului si sa aplice planificarea preemptiva .

7.8 Cooperarea si concurenta

Cand intr-un calculator exista mai multe procese care trebuie si pot fi executate in
acelasi timp , se spune ca ele sunt executate concurent . Dupa cum s-a mentionat , daca
sistemul de calcul este construit cu un singur procesor , executia concurenta a proceselor
se realizeaza prin intretesere . In cazul unui sistem de calcul multiprocesor, problema
intreteserii se pune numai atunci cand numarul proceselor care trebuie executate la un
moment dat depaseste pe cel al procesoarelor . Sistemele multiprocesor ofera posibilitatea
prelucrarii paralele, in timp ce sistmele monoprocesor o ofera numai pe cea a unei
prelucrari virtual paralele .
Indeplinirea activitatilor pe care trebuie sa le realizeze ansamblul de taskuri pentru
implementarea algoritmilor de control, implica cooperarea lor .
Aceasta consta din :
 sincronizarea dintre taskuri ;
 sincronizarea cu timpul-real ;
 excluderea mutuala de la utilizarea resurselor ;
 comunicarea dintre taskuri.
Este dificil de realizat o diferenţiere completă a acestor activităţi. De exemplu,
efectuând o operaţie de comunicare prin rendezvous, două taskuri realizează implicit o
sincronizare intre ele.

Sincronizarea cu timpul-real
În capitolul s-au prezentat rutinele tdelay, twake şi wait. Cea de-a doua rutină
serveşte la reactivarea unui task, la un moment precizat. Pentru execuţia periodică (cu
perioada deltat) a unei activităţi se presupune secvenţa:
repeat
today(deltat : tine);
*secvenţa de instrucţiuni a activităţii:
wait;
forever;

Trebuie remarcată diferenţa faţă de secvenţa:

repeat
today(deltat : tine);
wait;
*secvenţa de instrucţiuni a activităţii:
forever;

În primul caz, activitatea va începe întotdeauna virtual, cu perioada deltat. În cel


de-al doilea caz este fixă doar durata aşteptării, iar perioada de repetiţie a activiteţii nu
mai este constantă. Intervalul de timp cu care se repetă activitatea este mai mare decât
deltat, din cauza timpului necesar pentru execuţia secvenţei. Trebuie menţionat că durata
de execuţie a secvenţei de instrucţiuni poate fi variabilă.

Un alt mod de realizare a temporizărilor se bazează pe cutiile poştale, folosind


rutinele:

send (cutie: :pob : mes^ : mesage);


şi
receive ( cutie : pob: var mes ^mesage: deltat : time);

Întârzierea execuţiei unei secvenţe de instrucţiuni cu un interval de timp ”deltat”


se poate realiza cu secvenţa:

repeat
receive (cutie, mes, deltat);
*secvenţa de instrucţiuni:
forever

Execuţia periodică a unei secvenţe de instrucţiuni aplică doar două taskuri ca în


exemplul următor:
*secvenţa de iniţializare :
var cutie1, cutie2, cutie3 : pob;
deltat : time;
end;
send( cutie3, mes);
………….

task 1 is;
var mes : message;
………….
repeat
recive(cutie1,mes,deltat);
recive(cutie3,mes);
send(cutie3);
forever

task 2 is;
................
repeat
recive(cutie2,mes);
send(curie3,mes);
*secventa de instructiuni;
forever

Task 1 are numai un rol de planificator.Task 2 se repetă cu perioada fixată de task


1. Pentru fiecare task ce necesită asfel de întarzieri trebuie
construit câte un asfel de planificator.
O alta posibilitate de sincronizare cu timpul-real este oferita de conceptul
de rendezvous ( din Ada ) prin directiva select, cu opţiunea wait ( time ).

Sincronizarea proceselor intre ele

Sincronizarea constă din orice constrângere impusă ordonării operaţiilor în


timp, între două sau mai multe taskuri. Sincotizarea poate fi explicită, atunci când se
realizează folosind rutine special dedicate acestui scop, sau implicită, atunci când se
obţine ca un rezultat (auxiliar) al utizării unor rutine destinate altui scop. De exemplu
utilizând metodele suspend şi resume, sau synchronized ( din Java), se obţine o
sincronizare explicită, dar utilizând send şi recive, se obţine o sincronizare ca un
rezultat auxiliar al comunicării.
Sincronizarea dintre taskuri poate fi reciprocă sau unilaterală. Astfel
taskurile:

task 1 is;
begin
repeat
.........................
*evenimentul A;
*continuă dacă a avut loc evenimentul B;
.........................
forever
end task 1;

task 2 is;
begin
repeat
........................
*evenimentul B;
*continuă dacă a avut loc evenimentul A;
.......................
forever
end task 2;
se sincronizează reciproc. Fiecare trebuie să-l aştepte pe celălalt, dacă acesta nu a ajuns în
punctul de sincronizare.
Dacă în task 1 ar lipsi *continuă dacă a avut loc evenimentul B; atunci
s-ar obţine o sincronizare unilaterală. În acest caz, task 2 aşteaptă ca task 1 să efectueze
”evenimentul A”, şi chiar dacă nu a avut loc evenimentul B, task 1 îşi
va continua activitatea.

1. Sincronizarea cu rutinele delay şi continue


Exemplul 1:

Sincronizarea reciprocă:

*secvenţe de iniţalizare:
var queue1, queue2 : ^tcb;
begin
init (queue1);
init (queue2);
....................

task 1 is;
begin
repeat
......................
continue (queue2); t2
delay (queue1);
......................
forever end task 1;

task 2 is;
begin
repeat
......................
continue (queue1); t1
delay (queue2);
.....................
forever
end task 2;

Secvenţa de iniţializare este realizeată de un task oarecare (sau chiar de


unul din cele două taskuri care intră mai întâi în execuţie).

2. Sincronizarea cu primitivele P şi V

Exemplul 2:

Sincronizare reciprocă

*secvenţa de iniţializare:
var sen1, sem2 : semaphore;
begin
........................
init (sem1.next);
init (sem2.next);
sem1.val : =0;
sem2.val : =0;
........................

task 1 is; task 2 is;


begin begin
repeat repeat
............. .............
V(sem2); V(sem1);
P(sem1); P(sem2);
............ ............
forever forever
end task1; end task2;

Evenimentul de activare este aşteptat prin operaţia P şi se semnalează


prin operaţia V.
Dacă există certitudinea că unul dintre taskuri ”soseşte” mai repede în
punctul de sincronizare, atunci aceasta ( sincronizarea ) se poate realiza cu un
singur task.

Un semafor sp este propriu sau privat pentru un anumit task, dacă numai el
poate executa operatia P ( sp ). Celelalte taskuri pot executa numai operaţia V
( sp ).

Exemplul 3:

Sincronizarea a n taskuri

*secvenţa de iniţializare:
var sp,sm,se : semaphore;
b1,b2,...,bn : boolean;
end
begin
b1 :=true;
b2 :=false;
......................
bn := false;
init(sp.next);
init(sm.next);
init(se.next);
sm.val :=1
sp.val :=0
se.val :=0
....................
task1 is;
...................
P(sm);
If b1 and b2 and ... and bn thenV (sp);
else b1 :=false;
V(sm);
P(sp);
for 1 :=2 to n do V (sp);
...................
task i is;
......................
P (sm);
b1 :=true
if b1 and ... and bn then V(sp);
V(sm);
P(se);
..................
Secventele cuprinse intre P (sm) sunt sectiuni critice, si se execută prin excludere
mutuală.

3. Sincronizarea cu variabile de tip eveniment


Exemplul 4:

Sincronizare recipocă
*segvenţa de iniţializare:
Var ev1,ev2 : event;
begin
init(ev1.next);
init(ev2.next);
reset(ev1);
reset(ev2);
................

task 1 is; task 2 is;


begin begin
......... ..........
set(ev2); set(ev1);
wait(ev1) ; wait(ev2);
................ ...............
end task 1 end task 2

4. Sincronizarea cu rutinele send şi recive

Se pot utiliza pentru sincronizarea două cutii postale care sunt initial goale.
Un task va aştepta incercând sa citeasca un mesaj dintr-o cutie postală goala
şi-l va scoate pe altul din asteptare, depunand un mesaj in cutia lui postala.

5 Sincronizarea prin regiuni critice conditionate

Exemplul 5:
Sincronizare reciproca
*secventa de initializare:
var v:shared T;
b,b:boolean;
begin
b:=false;
b:=true;
............

task 1 is; task 2 is;


.............. .............
b:=true b:=true
with v then b do Q; with v then b do Q;
b:=false; b:=false;
.................. .......................

6. Sincronizarea prin rendezvous


In exemplu din figura 7.1 s-a realizat si o operatie de sincronizare in punctul
de intrare „nume3”.
Un alt mod de a realiza operatia de sincronizare prevede utilizarea in acest
scop a continuturilor procedurilor (a se vedea utilizarea cutii postale).

7. Sincronizarea prin expresii de cale

Exista mai multe variante pentru realizarea operatiei de sincronizare.


Exemplul 6:
Fie declaratia:

Path proc 1; proc 2;proc 3 end;

si urmatoarele doua task-uri:

task1 is; task 2 is;


............... ....................
proc 1; proc 2;
proc 3; ...................
..............

Este evidenta sincronizarea celor doua task-uri. In mod similar se poate realiza
sincronizarea mai multor task-uri.
In exemplul precedent, continuturile procedurilor nu au importanta pentru
sincronizare.
Un alt mod de realizare a sincronizari este acel care utilizarea conceputul de
obiect si se specifica utilizarea lui specifica.

Excluderea mutuala de la utilizarea resurselor

O resursa se numeste proprie, daca ea este utilizata numai de un singur task pe tot
parcursul activitatii ansamblului de taskuri. Ea se numeste comuna sau partajabila, daca
este urilizata de cel putin doua taskuri. O resursa se numeste partajabila cu n puncte de
acces (n>1), daca poate fi utilazata simultan de n taskuri. Ea se numeste resursa critica,
daca este comuna si are un singur punct de acces, adica, la un moment dat poate fi
utilizata numai de un singur task desi exista mai multe solicitari.

Mai multe taskuri care utilizeaza simultan, in comun, o resursa cu mai multe
puncte de acces, se numesc paralele din punctul de vedere al resursei. Daca mai multe
taskuri utilizeaza o resursa critica, atunci ele trebuie sa se excluda mutua l(sau reciproc,
in timp) de la folosirea ei. Secventa de instructini dintr-un task in care se utilizeaza
resursa critica se numeste sectiune critica.
O sectiune critica trebuie sa fie cat mai scurta posibil, astfel incat sa nu intarzie
prea mult celelalte taskuri(unele avand poate o prioritate mai mare). Prima varianta de
excludere reciproca a fost realizata hard, prin generarea unui semnal cand un task intra
intr-o sectiune critica. Acest smnal inhiba sistemul de intreruperi, astfel fiind impiedicate
alte taskuri sa intre in executia. Dezavantajul acestei metode consta din dificultate
realizari unor inhibari selective si dinamice.

1. Ecluderea mutuala cu rutinele delay si continue

Trebuie sa se construiasca un indicator de acces la secventa critica. Acesta va lua


valoarea false inainte de a intra in sectiune critica si true la iesire. Daca atunci cand
vrea sa intre in sectiunea critica indicatorul are valoarea false atunci asteapta pe coade
queue.

Exemplul 7 :
var ias: boolean; {indicator de acces la resurse}
queue: ^tcb;
end
*secventa de initializare:
ias:=true;
init (queue);

Un task care trebuie sa intre in sectiunea critica controlata de ias va executa


secventa:

if not ias then delay (queue);


ias:= false;
*utilizare resursa; {sectiune critica}
ias:=true;
continue (queue);

Inconvenientul acestei metode consta in faptul ca, daca un task a asteptat sa obtina
accesul la sectiunea sa critica si a fost reactivat, se poate intampla ca un alt task cu
prioritate mai mare ca a sa, sa ocupe resursa. Prin urmare, se poate ajunge la situatia in
care resursa sa fie utilizata in comun.
Anomalia nu apare daca exista siguranta ca accesul la variabila ias este invizibila,
mai precis ca primele doua linii ale secventei precedente sunt indivizibile.

2. Excluderea mutuala cu primitivele P si V

Primitivele P si V ofera posibilitatea unei metode simple si sigure pentru


excluderea mutuala. Aceasta consta din construirea unui semafor pentru fiecare resursa cu
un singur punct de acces.

S-ar putea să vă placă și