Sunteți pe pagina 1din 66

Introducere in programarea OCCAM

Cuprins:
1. Introducere
2. Indicatoare
3. Concepte
4. Fundamentele programarii OCCAM
5. Matrici in OCCAM
6. Comunicatia prin canale
7. Caractere si stringuri
8. Replicatori
9. Programarea in timp real in OCCAM
10.Configuratia
11.Terminarea programelor concurente
12.Stilul de programare OCCAM
1. Introducere
Scopul acestui curs este de a face o introducere in programarea
concurenta utilizind limbajul Occam. Vom oferi exemple de
programe Occam si vom discuta conceptele noi pe care le
aduce acest limbaj.
Occam a fost repede recunoscut ca o solutie la problema
programarii sistemelor concurente si ca o metoda puternica
si expresiva de descriere a algoritmilor concurenti.
Occam are o stransa legatura cu Transputer-ul INMOS, un
computer pe cip a carui arhitectura permite constructia
sistemelor paralele. Transputerul executa Occam mai mult
sau mai putin direct. (multe constructii Occam se traduc
in simple instructiuni ale transputerului).
Sistemele paralele pot fi proiectate in Occam si apoi
implementate folosind transputere ca "procese occam hard".
Aceasta relatie intima intre software si hardware poate fi
noua pentru multi dintre arhitectii de sisteme care sunt
probabil obisnuiti cu o diviziune mai rigida a muncii.
Acest manual nu se adreseaza neaparat programatorilor
profesionisti, ci mai degraba inginerilor profesionisti
care doresc sa foloseasca Occam-ul pentru descrierea si
proiectarea sistemelor hardware.
De aceea nu este nevoie de cunoasterea foarte bine a nici
unui limbaj de programare de nivel inalt sau limbaj masina,
dar trebuie sa fim familiarizati cu conceptele generale
despre calculatoare si programare.
Nu vom insista asupra nici unei combinatii particulare de
calculator/compilator.
2. Indicatoare
Materialul este presarat cu indicatoare pentru a semnala
punctele de interes deosebit.
Inicatoarele folosite sunt:
Atentie:
Sectiunile marcate sunt acelea care ar putea pacali
incepatorii in programarea Occam. Aceasta se poate
intimpla din urmatoarele motive:
1. Conceptul este in sine dificil.
2. Occam trateaza aceasta problema in mod diferit de
limbajele traditionale cu care suntem obisnuiti.
3. Datorita unor limitari sau restrictii in implementarile
curente de Occam.
Aceste sectiuni vor trebui bine recitite mai ales daca
ati scris un program Occam care nu merge.
Sfat: Acestea sunt trucuri si metode care s-au dovedit folositoare
autorului in timp ce a invatat Occam-ul.
Ideea: Un concept care este fundamental pentru intelegerea
Occam-ului. Fiti siguri ca l-ati inteles.
Obs: O scurta digresiune de la subiectul principal al manualului
in chestiuni mai largi despre calculatoare.
Nota: Explicatii scurte despre anumite implementari. Daca nu sunt
prea clare nu va obositi sa le intelegeti.
3. Concepte
Concurenta
De cand John Von Neumann a descoperit principiile acum mai bine
de 40 de ani, toate calculatoarele digitale au fost construite
fundamental la fel.
Un procesor cu un set de operatii numerice, conectat la o
memorie care contine numere. O parte din numere sunt datele
pe care le proceseaza calculatorul, iar o parte sunt
instructiunile pe care procesorul le executa una dupa alta
in secventa.
Instructiunile sunt pasate procesorului una dupa alta si
executate. Executia unui program este secventiala, constind
dintr-o serie de actiuni primitive ce se succed una dupa alta
in timp.
Exemple de zi cu zi de activitati similare din lumea reala pot
fi citirea unei carti (cuvant cu cuvant), sau executia unui model
de tricotaj prin urmarirea in ordine a instructiunilor.
Calculatoarele sunt construite mai ales pentru a modela lumea
reala. Chiar si simpla adunare a lui 2 cu 2 este un model al
lumii reale, mai putin atunci cand este facut de un matematician
care este interesat in proprietatile pure ale numerelor. Mult mai
frecvent, 2 + 2 este un model pentru actiunea de adaugare a
2 kg, sau 2 mii de lei, sau 2 mere, sau 2 avioane la un stoc
existent de alte 2.
Desigur aplicatiile de baza ale calculatoarelor cum ar fi
contabilitatea, activitatile bancare, prognoza vremii, controlul
proceselor sau procesarea textelor sunt explicit modelari de
obiecte, evenimente sau activitati din lumea reala.
Lumea pe care o traim este in esenta concurenta. Pe scara
afacerilor umane, si de fapt pe orice scala intre mecanica
cosmica si cea cuantica, lumea se comporta ca si cum ar fi
organizata in 3 dimensiuni spatiale si una temporala.
Evenimentele se intimpla simultan in timp si spatiu. Este posibil
ca 2 evenimente sa apara in acelasi loc unul dupa altul (adica
secvential), dar la fel de probabil este ca evenimentele sa
apara in locuri diferite in acelasi timp (adica concurent, paralel).
Concurenta este o caracteristica atat de naturala a universului
incat in mod normal nici nu ne preocupa. De exemplu, faptul ca
populatia planetei traieste vieti diferite in locuri diferite
in acelasi timp este asa de firesc incat este de-a dreptul
stinjenitor sa il constatam.
Totusi merita sa subliniem contrastul dintre natura concurenta
a lumii si cea secventiala a calculatoarelor digitale. Cum scopul
principal al calculatoarelor este sa modeleze cat mai bine
lumea, se pare ca avem o serioasa nepotrivire.
Pentru a modela lumea cu un calculator, programatorii
calculatoarelor conventionale trebuie sa gaseasca metode de a
imita evenimentele concurente folosind o secventa de instructiuni.
Aceasta nu este o problema daca aplicatia este una contabila,
unde este perfect rezonabil sa privim lucrurile desfacute,
materialele sau banii intrind sau iesind secvential in timp.
Este mai degraba o problema atunci cand vrem sa controlam un
experiment petro-chimic de exemplu. Fiecare proces din fiecare
parte a experimentului trebuie sa fie supravegheat si controlat
in acelasi timp. Nu se poate sa lasam nesupravegheata o reactie
numai pentru ca in acel moment calcultorul se intampla sa se
uite la alt reactor.
Programarea concurenta
Primele calculatoare digitale erau programate folosind
instructiuni numerice intelese de procesor. Aceasta programare
este asa de greoaie si de supusa la erori incat oamenii de
stiinta calculatoarelor au inceput sa proiecteze limbaje de
nivel inalt.
Aceste limbaje permit programatorilor sa exprime logica unui
program in notatii care folosesc cuvinte citibile din engleza
sau franceza, cu o sintaxa limitata. Apoi, un program numit
compilator translateaza aceste notatii in instructiuni numerice
intelese de calculator.
Pentru majoritatea limbajelor, rezultatul compilarii este tot
o secventa de instructiuni care trebuie executate pe rand de
procesor. Cu alte cuvinte, aceste limbaje reflecta fidel natura
masinii secventiale von Neumann pe care programele se executa,
intr-o forma mai comoda programatorilor umani.
Pentru a modela cat mai bine concurenta lumii reale, este de
preferat sa avem mai multe procesoare care sa lucreze in paralel
la acelasi program. Astfel am obtine si performante mai mari.
Oricat de mult ar creste performantele unui procesor, 10 procesoare
in paralel tot vor executa de 10 ori mai multe instructiuni
pe secunda.
Limbajele de programare conventionale nu sunt bine echipate pentru
construirea de programe pentru astfel de arhitecturi cu mai multe
procesoare, deoarece proiectarea lor a presupus executia secventiala
a instructiunilor.
Cateva limbaje de programare au fost modificate pentru a
permite scrierea de programe concurente. Sincronizarea intre
procese a fost lasata pe seama programatorului care are acum
de rezolvat o problema mai complicata decat cea secventiala.
Occam este primul limbaj bazat pe conceptul de paralelism
pe langa executia secventiala si care asigura comunicatia
si sincronizarea automata intre procese.
Sincronizarea
Este posibil sa scriem programe concurente in limbaje de
programare conventionale si apoi sa le rulam pe calculatoare
conventionale. Ce se intimpla de fapt este ca programatorul
scrie mai multe programe iar calculatorul se face ca le
executa pe toate simultan, rulind de fapt pe rind cate o bucatica
din fiecare, comutind executia de la unul la altul la intervale
scurte pina cand toate se termina.
Totusi, acest mod de programare este mult mai dificil dacat metoda
secventiala directa: "fa asta, apoi fa asta, apoi fa asta". Spus
mai pe sleau, aceasta pentru ca programarea concurenta are doar
un inceput si un sfirsit, pe cand cea concurenta are mai multe
inceputuri si mai multe sfarsituri.
Un program secvential incepe, se executa si apoi se termina. Ori
se ruleaza ori nu. De multe ori nici nu ne intereseaza cand se
termina (desi de obicei dorim se fie cat mai repede).
Sa consideram ca vrem sa tricotam un jerseu. Modelul consta
dintr-o lista de instructiuni cum sa folosim lana si andrelele
care daca o respectam ne va conduce la realizarea jerseului.
Anumite instructiuni trebuie repetate de mai multe ori, iar
pentru asta modelul foloseste notatii adecvate fara a fi nevoie
sa scrie fiecare pas in parte, la fel cum limbajele de programare
folosesc structurile repetitive.
In cazul unui singur lucrator care nu se grabeste, jerseul va
dura cat dureaza tricotatul lui. Va fi gata dupa ce a executat
ultima instructiune din model.
In occam acest lucru poate fi reprezentat:
SEQ
...tricoteaza corpul
...tricoteaza maneca
...tricoteaza maneca
...tricoteaza guler
unde SEQ inseamna "executa toate operatiile urmatoare in secventa".
Obs: ... sunt folosite pentru a descrie portiuni de program a
caror detaliere este irelevanta.
... nu sunt comentarii. Comentariile in Occam incep cu --
Sa consideram acum cazul cand exista o firma cu mai multi lucratori
care impletesc jerseul pe componente (corp, maneci, guler), fiecare
lucrind la o componenta si care au o limita de timp pentru a
termina.
Treaba se poate imparti: o parte din ei sa lucreze la corp,
o parte la maneci, o alta parte la gat si alta la coaserea
coponentelor.
Totusi, degeaba unii termina repede cu manecile si gatul daca
cei care lucreaza in paralel la corp nu au terminat. Jerseul
nu poate fi cusut decat dupa ce se termina toate componentele.
Astfel, daca lucratorii nu se sincronizeaza bine, toti vor
astepta lucratorul mai lent.
Calculatoarele amplifica enorm aceasta problema. Ele nu sunt nici
asa de inteligente sau rabdatoare nici cat cel mai nervos dintre
lucratori. Daca mai multe programe cooperante nu isi termina
partea de job la timp, rezultatul este de obicei ca programul
nu merge deloc, decat ca merge ineficient.
In alt sens, calculatoarele sunt foarte rabdatoare. Un program este
perfect pregatit sa astepte ceva care nu se va intimpla niciodata,
pentru ca sincronizarea este defectuoasa. Aceasta situatie este
cunoscuta in programarea concurenta ca blocare.
De aceea, scrierea programelor concurente este dificila. Asigurarea
sincronizarii intre parti a fost pana acum in mare parte datoria
programatorului, care era nevoit sa scrie un sistrem elaborat
de semnale prin care fiecare parte sa le anunte pe celelalte daca
este sa nu gata.
Fiecare parte de program, trebuie sa se uite continuu la aceste
semnale ca sa stie daca continua sau nu. Codul pentru aceste
lucruri este considerabil, iar scrierea lui este costisitoare
ca timp. In acest timp, programatorul putea sa se preocupe cu
acele parti din program care de fapt fac treaba.
Dat un program concurent de orice complexitate, programatorului
ii este foarte dificil sa inteleaga macar cum trebuie sa se
coreleze partile intre ele.
Occam simplifica scrierea programelor concurente, asigurand
sincronizarea. De exemplu, tricotarea paralela s-ar descrie:
SEQ
PAR
...tricoteaza corp
...tricoteaza maneca stanga
...tricoteaza maneca dreapta
...tricoteaza guler
...coase jerseul
Acest lucru inseamna ca tricotarea componentelor se face in
paralel (PAR), dar coaserea urmeaza in secventa dupa ce toate
componentele au fost tricotate.
Comunicatia intre diferite parti ale unui program este incorporata
in limbaj si este o comunicatie sincrona adica un mesaj este
trimis doar daca si transmitatorul si receptorul sunt pregatiti.
Daca una dintre parti este gata inaintea celeilalte, va astepta
automat ca si cealalta parte sa devina pregatita. Singura
responsabilitate ramasa pe seama programatorului este aceea
de a evita blocarea prin asigurarea ca a doua parte va deveni
gata candva.
Putem adauga astfel de comunicatie la descrierea tricotatului:
PAR
SEQ -- lucratorul pt corp
...tricoteaza corp
...trimite corp
SEQ -- lucratorul pentru maneci
...tricoteaza maneca stanga
...trimite maneca stanga
...tricoteaza maneca dreapta
...trimite maneca dreapta
SEQ -- lucratorul pentru guler
...tricoteaza gulerul
...trimite gulerul
SEQ -- cusatorul
PAR
...primeste corp
SEQ
...primeste maneca stanga
...primeste maneca dreapta
...primeste guler
...coase jerseul
Aceasta este o descriere a cum se poate face un jerseu de catre
4 lucratori care lucreaza in paralel, toata sincronizarea fiind
inclusa in structura descrierii. Functioneaza prin combinarea
proceselor simple (tricoteaza corp) in procese mai largi (fiecare
lucrator), care pot fi combinate in procese si mai mari (jerseu).
Procese si canale
In Occam partile unui program le numim procese.
Ideea: Un proces porneste, executa niste operatii si se termina.
In Occam mai multe procese se pot executa in acelasi timp si
procesele isi trimit mesaje unele altora.
In limbajele conventionale, cum ar fi BASIC, mare parte din
activitate este petrecuta cu schimbarea valorilor. De ex:
10 LET A = 2
20 LET B = A
40 PRINT B
50 END
Exista un rudiment de comunicatie in acest program: comanda
PRINT asigura comunicatia intre program si ecran. Mai exista
un fel de comunicatie in urma careia valoarea 2 este trimisa
de la A la B, desi acest lucru nu merita sa fie numit comunicatie
deoarece nu exista decat un singur program BASIC care se executa
linie dupa linie. Mai degraba zicem ca valoarea 2 este memorata
atat in A cat si in B.
Sa presupunem ca avem 2 calculatoare conectate cumva intre ele
si 2 programe care se executa in paralel.
----------------------+ +-----------------------
| |
10 LET A = 2 | \/\/\/\/\/| 10 LET B = A
20 END | magic | 20 END
| |
----------------------+ +-----------------------
computer 1 computer 2
Rezultatul dorit este acela ca valoarea lui A traverseaza cumva
prapastia dintre cele 2 calculatoare si B ia valoarea 2.
Pentru a realiza acesta comunicatie in BASIC, putem conecta
calculatoarele pe o linie seriala RS232 si ambele programe
trebuie completate cu linii suplimentare pentru configurarea
comunicatiei si trimiterea datelor.
----------------------+ +-----------------------
| |
10 LET A = 2 | \/\/\/\/\/| 10 RSCONFIG ..
20 RSCONFIG .. | RS232 | 20 RSINPUT A
30 RSOUTPUT A | | 30 LET B = A
40 END | | 40 END
----------------------+ +-----------------------
computer 1 computer 2
Occam permite acest fel de comunicatie ca o caracteristica
normala de programare si nu necesita instructiuni speciale
care trebuie sa fie diferite pentru fiecare fel de dispozitiv
de comunicatie.
Mai mult, Occam nu este interesat daca cele 2 programe care
comunica se executa pe acelasi calculator sau pe calculatoare
diferite.
Asa cum foloseste variabile pentru memorarea valorilor, Occam
foloseste canale pentru transmiterea valorilor. Canalele se
aseamana mult cu variabilele,doar ca in loc sa le asignam valori
(ex LET A = 2 in BASIC), le trimitem sau primim de le ele valori.
Valoarea trimisa de un proces este primita de alt proces. Un canal
poate lega doar 2 procese. Canalele sunt unidirectionale, asa ca
este nevoie de 2 canale pentru o comunicatie bidirectionala.
Ideea: Un canal este o legatura unidirectionala, punct la punct
intre 2 procese.
Un transfer pe un canal este de fapt o copiere; daca valoarea
unei variabile este trimisa pe un canal, variabila ramane
nemodificata, pe canal trimitandu-se o copie.
Occam foloseste ! pentru trimitere si ? pentru receptie.
--------------------+ +------------------
| |
A ! 2 |\/\/\/\/\/\| A ? B
| |
--------------------+ +------------------
proces 1 proces 2
unde A este un canal, iar B este o variabila.
A ! 2 trimite 2 la A
A ? B citeste de la A in B.
Cum cele 2 procese sunt independente, tranferul nu se poate
realiza decat atunci cand ambele procese sunt pregatite.
In alte cuvinte, daca procesul 1 se executa inainte de procesul 2,
el va fi blocat automat in asteptarea procesului 2, si invers daca
intrarea in procesul 2 se executa inainte ca procesul 1 sa execute
iesirea, procesul 2 va astepta sa apara o valoare. Nu e nici o sansa
ca o valoare sa fie trimisa in eter si pierduta.
Revenind la programele BASIC, nu avem aceste asigurari. Este
foarte posibil ori sa pierdem date ori sa avem mesaje de eroare
de tipul "conexiune proasta".
Daca programul 1 ajunge la RSOUTPUT A inainte ca programul 2 sa
ajunga la RSINPUT A, valoarea lui A va fi trimisa si pierduta.
Cele 2 caracteristici importante care deosebesc canalele de
variabile sunt:
1) Un canal poate transfera valori intre 2 procese care se executa
pe acelasi calculator, sau intre 2 procese care se executa pe
calculatoare diferite. In primul caz, canalul este de fapt o
locatie de memorie ca o variabila. In al doilea caz, canalul
este o legatura hard reala cum ar fi de exemplu o legatura a
unui transputer sau o linie seriala. Ambele situatii sunt
reprezentate la fel in Occam.
Ideea: Un canal Occam descrie abstract comunicatia, fiind
independent de implementarea fizica. Se poate scrie
si testa un program folosind canale, fara a fi preocupati
de locul exact unde se vor executa diferitele procese.
Programul poate fi scris pe o singura statie. Cand este
gata, putem decide sa distribuim diferite procese ale
programului pe calculatoare diferite facand simple
declaratii la inceputul programului.
2) Canalele sunt rabdatoare si politicoase. Daca un proces
de primire nu are nici o valoare pregatita, va astepta fara
instructiuni suplimentare din partea programatorului.
Analog, transmitatorul nu va trimite pana cand receptorul nu este
gata.
Putem descrie acum tricotarea paralela a jerseului folosind
canale pentru transportul componentelor.
PAR
SEQ
...tricoteaza corpul
canalcorp ! corp
SEQ
...tricoteaza maneca stanga
canalmaneca ! maneca.stanga
...tricoteaza maneca dreapta
canalmaneca ! maneca.dreapta
SEQ
...tricoteaza guler
canalguler ! guler
SEQ
PAR
canalcorp ? corp
SEQ
canalmaneca ? maneca.stanga
canalmaneca ? maneca.dreapta
canalguler ? guler
...coase jerseu
Avem canale diferite pentru ca un canal nu poate lega decat
2 procese. De exemplu, canalcorp leaga lucratorul corpului cu
lucratorul care finiseaza jerseul. Daca Occam este folosit ca
limbaj de programare si nu ca limbaj de descriere ca in acest
exemplu, canalele si variabilele trebuie declarate inainte de
a fi folosite.
Comunicatia prin canale auto-sincronizate este o caracteristica
noua si puternica a Occam-ului si face din scrierea programelor
concurente un job mult mai putin spectaculos decat este privita
ea in scrierea in limbajele conventionale.
4. Fundamentele programarii in Occam
Procese primitive
Toate programele Occam sunt formate din combinatii de procese
primitive. Exista 5 tipuri de procese primitive:
atribuire, input, output, SKIP si STOP.
Procesul de atribuire
Schimba valoarea unei variabile, asa cum se face in majoritatea
limbajelor conventionale. Simbolul pentru atribuire in Occam
este :=. De exemplu, procesul de atribuire
fred := 2
are ca efect schimbarea valorii variabilei fred in 2. Valoarea
asignata unei variabile poate fi o expresie:
fred := 2 + 5
iar expresia poate contine si ea variabile:
fred := 5 - jim
Atentie := inseamna atribuire,
iar = inseamna test de egalitate.
Procesul input
Citeste intr-o variabila o valoare de pe un canal. Simbolul pentru
input in Occam este ?. Procesul de intrare:
chan3 ? fred
ia valoarea de pe canalul chan3 si o pune in variabila fred.
Obs: in partea dreapta trebuie sa fie obligatoriu o variabila,
iar in partea stanga un canal. Nu are sens sa facem intrare
intr-o constanta sau intr-o expresie.
Un proces de intrare se blocheaza pana cand procesul de iesire
corespondent de la celalalt capat al canalului se executa.
Sfat: Pentru memorare ganditi "Unde este valoarea mea ?"
Procesul output
Trimite o valoare pe un canal. Simbolul pentru output in Occam este
!. De exemplu:
chan3 ! 2
trimite valoarea 2 pe canalul chan3.
Obs: in partea stanga trebuie sa fie un canal,
iar in partea dreapta orice poate fi atribuit unei variabile.
Sfat: Pentru memorare ganditi-va la :"Uite valoarea !"
Comunicatia
Comunicatia printr-un canal poate apare doar daca ambele procese
input si output sunt pregatite. Daca in timpul executiei unui
program, se ajunge la un proces input inainte ca procesul output
corespunzator sa fie atins, atunci procesul input este blocat
pana cand procesul output devine gata. Daca se ajunge mai
intai la procesul output, acesta va astepta ca procesul input
sa devina gata.
Valoarea trimisa pe un canal este copiata in variabila de intrare,
iar variabila de iesire ramane neschimbata.
Ideea: Comunicatia este sincrona.
Procesele primitive din Occam ocupa fiecare o linie de program.
Ele sunt caramizile din care se construiesc procese mai complexe.
Ideea: Programele Occam sunt construite prin combinarea proceselor
primitive.
Procesele SKIP si STOP
Occam are 2 procese primitive speciale: SKIP si STOP.
Ideea: Procesul SKIP porneste, nu face nimic si se opreste.
SKIP poate fi gandit ca reprezentind un proces care nu face nimic.
Poate fi folosit intr-un program partial terminat in locul unui
proces care va fi scris mai tarziu, dar care pentru moment i se
permite sa nu faca nimic.
De exemplu, un proces care trebuie sa comande un motor electric
poate fi inlocuit cu SKIP cand testam testam programul fara motor.
Exista de asemeni situatii cand nu vrem sa se intample nimic, dar
sintaxa Occam ne obliga sa folosim un proces.
Ideea: Procesul STOP porneste, nu face nimic si nu se termina
niciodata.
STOP poate fi privit ca un proces defect. Ca si SKIP poate fi
folosit in locul unor procese inca nedezvoltate.
Un loc potrivit pentru STOP ar fi un proces nescris de tratare
a erorilor.
STOP poate avea un efect de raspandire, in sensul ca alte
procese care comunica cu el nu se vor mai termina.
Terminarea si blocarea proceselor
Un proces care a executat toate actiunile se zice ca s-a terminat.
In mod normal, un proces porneste, se executa si apoi se termina.
Un proces care nu se poate executa se zice ca este oprit. Un proces
oprit nu se termina niciodata. Un proces poate fi oprit in asteptarea
unui eveniment care nu se va intimpla niciodata datorita unei
erori de programare. In acest caz procesul se cheama blocat.
Terminarea corecta a programelor concurente nu este o problema
simpla, deoarece ele pot avea multe procese paralele care comunica
unele cu altele.
Constructori
Mai multe procese primitive pot fi combinate intr-un proces
mai mare specificand ca ele trebuie executate unul dupa altul,
sau toate deodata. Acest proces mai mare este numit o constructie
si incepe cu un cuvant cheie care indica cum sunt combinate
procesele componente.
Constructorul SEQ
Cel mai simplu de inteles constructor este SEQ (pronuntat "sic"),
care zice "executa urmatoarele procese unul dupa altul".
SEQ
chan3 ? fred
jim := fred + 1
chan4 ! jim
Aceste linii se traduc: "executa in secventa intrarea de pe canalul
chan3 in variabila fred, apoi atribuie variabilei jim valoarea lui
fred + 1, apoi trimite pe canalul chan4 valoarea lui jim.
Procesele componente sunt indentate cu 2 caractere fata de cuvantul
cheie SEQ, asa ca ele sunt aliniate sub litera Q. Asta nu pentru
ca programele sa arate mai frumos, ci pentru ca Occam-ul sa stie
ce procese fac parte din SEQ.
De fiecare data cand folosim un constructor, indentam componentele
lui la 2 caractere fata de constructor. Alte limbaje folosesc
caractere speciale cum ar fi {...} sau begin...end in acest scop.
Occam foloseste doar indentarea.
Ideea: O constructie SEQ se termina cand ultimul proces se termina.
Atentie:
SEQ este obligatoriu atunci cand 2 sau mai multe procese
trebuie executate in secventa. In limbajele conventionale
era suficient sa scriem instructiunile una dupa alta pentru
ca ele sa fie executate in secventa. Deoarece Occam ofera
si alte modalitati de executie diferite de cel secvential,
executia in secventa trebuie specificata explicit.
Constructorul PAR
Constructorul PAR, prescurtare de la paralel inseamna:
"executa urmatoarele procese in acelasi timp". Toate componentele
unui PAR incep simultan executia. De exemplu:
PAR
SEQ
chan3 ? fred
fred := fred + 1
SEQ
chan4 ? jim
jim := jim + 1
inseamna: "excuta intrarea de pe canalul chan3 in variabila fred
dupa care incrementeaza valoarea lui fred, iar in acest timp
citeste de pe canalul chan4 in variabila jim dupa care incrementeaza
valoarea citita".
Observati din nou indentarea. Indentarea primelor 2 caractere
spune ca procesul PAR contine 2 procese SEQ. Al doilea nivel de
indentare ne spune ca fiecare SEQ este compus din cate 2 procese
primitive.
Intr-o constructie PAR nu conteaza in ce ordine scriem componentele,
deoarece ele se executa simultan. PAR este mai greu de inteles decat
SEQ deoarece idea ca mai multe lucruri intr-un calculator se pot
intimpla simultan este noua multor programatori.
De exemplu acum nu mai stim care din cele 2 procese paralele din
exemplul de mai sus se termina mai intai. Depinde de care din intrari
devine disponibila mai intai, care depinde de alte 2 procese de
iesire care se executa in alta parte in program.
Frumusetea Occam-ului este ca acestea nu prea conteaza, deoarece
constructorul PAR in sine are un singur inceput bine definit si
un singur sfarsit la fel de bine definit. Stim ca 2 procese SEQ
vor porni in acelasi timp, se vor executa cand intrarile lor
vor deveni disponibile si apoi se vor termina.
Toate componentele dintr-un PAR incep deodata, iar PAR se termina
cand toate componentele s-au terminat, iar asta este tot ce ne
intereseaza.
Ideea: Acesta este principiul central al programarii Occam:
procese compuse din procese mai simple se comporta la
fel ca procesele simple, adica incep, se executa si
se termina. La randul lor pot deveni componentele unor
procese si mai complexe.
Sunt multe de spus in legatura cu PAR, mai ales in legatura cu
comunicatia prin canale. Mai mult, existamulti alti constructori
in Occam care construiesc procesele ce se repeta sau care fac
alegeri conditionate.
Dar inainte de a trece la astfel de aspecte mai trebuie clarificat
ceva. Pana acum am folosit canale si variabile ca chan3 sau fred
ca si cand sa spunem ar creste in copaci. In nici un caz; in Occam
atat canalele cat si variabilele trebuie specificate inainte de a
fi folosite. De aceea vom discuta specificatii si tipuri inainte de
a trece mai departe asa incat exemplele pe care le vom studia sa
fie programe Occam valide.
Tipuri, specificatii si domenii
Occam la fel ca Pascal si multe alte limbaje (nu la fel ca BASIC)
solicita ca fiecare obiect obiect folosit de un program sa aiba un
tip care sa-i spuna cu ce fel de obiect are de-a face. Mai mult,
specificarea tipului obiectului trebuie facuta inainte de folosirea
lui.
Nume
Sa incepem cu numele. In Occam numele obiectelor pot fi cat de lungi
dorim dar trebuie sa inceapa cu o litera. Restul numelui poate fi
alcatuit din litere, cifre sau puncte. Se face deosevire intre
litere mari si litere mici.
Nume valide:
x y fred chan3 Chan3 new.fred old.fred
Cuvintele cheie ca SEQ, PAR sau CHAN sunt cu litere mari si
sunt rezervate.
Nume invalide:
3chan -- nu incepe cu o litera
old-fred -- contine un caracter ilegal '-'
fred$ -- contine un caracter ilegal '$'
old fred -- contine un spatiu
CHAN -- cuvant rezervat
Tipuri de date
Variabilele pot lua valori de diferite tipuri. Iata cateva din
tipurile de date care sunt oferite intotdeauna de Occam:
INT -- intreg
BYTE -- intreg intre 0 si 255, utilizat si in
-- reprezentarea caracterelor.
BOOL -- una dintre valorile logice TRUE sau FALSE
Declararea tipului unei variabile se face:
INT fred, jim :
care inseamna ca fred si jim pot fi folosite pentru reprezentarea
numerelor intregi. Mai multe variabile pot fi specificate deodata
prin listarea lor despartite de virgula.
Nota: Mai exista si alte tipuri de date de baza:
INT16, INT32, INT64, REAL32, REAL64.
Vom folosi doar INT, BYTE si BOOL fara a face nici o
presupunere asupra dimensiunii lui INT.
Tipul si protocolul canalelor
Toate canalele sunt de tipul CHAN OF protocol. Protolcolul
specifica tipul datelor transmise prin canal. Deocamdata ne vom
multumi sa privim canalele transportind valori singulare de un
singur tip de date.
Un canal care transporta valori intregi singulare se specifica:
CHAN OF INT chan3 :
unde INT tipul datelor care poate fi trimise pe canalul chan3.
Tipul lui chan3 este CHAN OF INT. In general protocolul unui
canal se specifica prin CHAN OF protocol.
Tipul TIMER
Tipul TIMER permite crearea de timere care pot fi folosite drept
ceasuri de procese.
Caractere si stringuri
Occam nu are tipul CHAR sau STRING. In schimb caracterele se
reprezinta cu tipul BYTE, iar sirurile de caractere ca matrici
de tipul BYTE.
Tipul boolean
Valorile booleene, sau valorile de adevar sunt rezultatele testelor
operatorilor de comparatie. Occam ofera urmatoarele teste:
= -- egal
<> -- diferit
> -- mai mare
< -- mai mic
>= -- mai mare sau egal
<= -- mai mic sau egal
Aceste teste pot fi facute doar asupra a 2 valori de acelasi tip
si rezultatul lor este totdeauna de tipul BOOL.
Valorile TRUE si FALSE sunt constante Occam.
Constante
Se poate da un nume unei constante folosind
VAL tip nume IS valoare:
Deci putem scrie:
VAL INT an IS 365:
VAL INT an.bisect IS 366:
Tipul constantei poate fi omis, fiind dedus din valoare
VAL an IS 365:
Ambiguitatile posibile intre BYTE si INT se rezolva specificind
explicit tipul valorii.
Observati cele 2 puncte (:) folosite la terminarea diferitelor
specificatii. Ele au rolul de a lega specificarea de procesul
ce ii urmeaza.
Domeniu
In Occam variabilele, canalele si alte obiecte cu nume sunt
locale procesului care urmeaza imediat declararii lor.
Asta inseamna ca ca obiectul la care numele se refera
nu exista in nici un alt proces. In exemplul:
PAR
INT fred:
SEQ
chan3 ? fred
...alte procese
INT jim:
SEQ
chan4 ? fred
...alte procese
se va raporta o eroare, deoarece fred exista doar in procesul
primului SEQ, iar jim exista doar in interiorul celui de-al
doilea SEQ. Al doilea fred este o variabila nedeclarata.
Cele 2 puncte (:) specifica domeniul de valabilitate al numelui
declarat si anume procesul ce urmeaza imediat declararii. Acest
proces care ii urmeaza este domeniul de valabilitate pentru care
este valabila specificarea.
Acelasi nume poate fi folosit pentru obiecte diferite cu
domenii diferite. De exemplu putem folosi fred pentru ambele
variabile din urmatorul exemplu:
PAR
INT fred:
SEQ
chan3 ? fred
...alte procese
INT fred:
SEQ
chan4 ? fred
...alte procese
Cei 2 fred sunt variabile diferite cu domenii diferite: fiecare
este local SEQ-ului care urmeaza declararii.
Daca in domeniul unei variabile este declarata alta variabila
cu acelasi nume, numele va fi asociat cu variabila noua.
INT fred:
SEQ
chan3 ? fred
INT fred:
SEQ
chan4 ? fred
...alte procese
...alte procese
Intrarea de pe chan4 merge in al doilea fred, primul fred fiind
mascat in al doilea SEQ.
Sa completam exemplul cu PAR definit mai devreme cu declaratiile
corecte:
CHAN OF INT chan3, chan4:
PAR
INT fred:
SEQ
chan3 ? fred
fred := fred + 1
INT jim:
SEQ
chan4 ? jim
jim := jim + 1
Acum canalele chan3 si chan4 sunt cunoscute in procesul PAR.
Le putem folosi corect in oricare din cele 2 SEQ-uri. Pe de
alta parte fred si jim sunt cunoscute doar in SEQ-urile
fiecaruia.
Atentie:
Variabilele in Occam nu sunt initializate la declarare.
Valoarea unei variabile este nedefinita pana cand i se
atribuie o valoare sau pana cand se face un input pe ea.
Valoarea unei variabile are sens doar in timpul executiei
procesului pentru care este declarata. Cum variabila nu
exista in afara acestui proces, nu are sens sa vorbim de
valoarea ei in afara procesului. Si mai important, nu are
sens sa vorbim de valoarea ei dupa ce procesul s-a
terminat. Data viitoare cand se va executa procesul,
variabila incepe din nou sa fie nedefinita. Nu putem si
nu trebuie sa credem ca isi pastreaza valoarea de la
terminarea executiei precedente. De exemplu:
WHILE x >= 0 -- ilegal, x nu este declarat
INT x:
SEQ
input ? x
output ! x
INT x:
WHILE x >= 0 -- gresit, x nu este initializat
SEQ
input ? x
output ! x
INT x:
SEQ
x := 0
WHILE x >= 0 -- corect
SEQ
input ? x
output ! x
(WHILE este o metoda prin care se specifica executia repetitiva a
unui proces in Occam).
Sfat: Daca dorim ca o variabila sa-si pastreze valoarea de la o
executie la alta a procesului, ea trebuie declarata in
domeniul exterior, care contine procesul.
Comunicatia intre procese
Comunicatia intre procese este esenta programarii Occam.
In cel mai simplu mod necesita 2 procese ce se executa in paralel
si un canal ce le leaga:
INT x:
CHAN OF INT comm:
PAR
comm ! 2
comm ? x
Acest program trimite valoarea 2 dintr-un proces altui proces
care o primeste in variabila x. Efectul lui este acelasi cu
un singur proces care atribuie variabilei x valoarea 2.
Variabile partajate: un avertisment
Comunicatia intre componentele unui PAR trebuie sa se faca doar
folosind canale. Occam nu permite transmiterea de valori intre
procese paralele prin variabile comune.
Daca o componenta a unui PAR contine o atribuire la o variabila,
atunci acea variabila nu poate fi utilizata deloc in alte
componente.
INT x, y:
PAR
SEQ
x := 2
...alte procese
SEQ
y := x -- ilegal
...alte procese
Obs: Corect este sa pastram variabilele locale componentelor
si sa folosim canalele pentru comunicatie.
Aceasta pare o restrictie severa pentru programatorii cu experienta
in limbajele conventionale. Cu siguranta va fi cea mai mare sursa
de erori pentru incepatorii in programarea Occam.
Ca toate prohibitiile va fi mult mai usor de suportat daca se
intelege motivul ei. Motivul este la fel de simplu cat este de
necesar.
Procesele paralele se executa in acelasi timp si in general
asincron, adica fiecare in ritmul lui, sincronizindu-se cu
celelalte cind trebuie sa comunice printr-un canal.
Daca Occam-ul ar permite unui proces paralel sa citeasca o variabila
a carei valoare este alterata de alt proces paralel, ce valoare
va citi de fapt? Depinde daca celalalt proces a apucat sa o altereze
sau nu. Acest lucru nu se stie pentru ca procesele sunt asincrone.
Si ce se intampla daca al doilea proces schimba valoarea variabilei
chiar in momentul cand primul proces vrea s-o citeasca.
O astfel de schema este inacceptabila si de aceea interzisa. Dar nu
am putea sa ne organizam astfel incat sa avertizam celalalt proces
cand o variabila isi schimba valoarea? Sigur ca da, iar obiectul
rezultat exista deja in Occam si se cheama canal!
Idea: In Occam variabilele sunt folosite pentru a memora valori,
iar canalele pentru comunicarea valorilor.
Sa studiem un exemplu mai complicat al unui PAR care efectueaza
putina aritmetica inainte de a trimite o variabila pe un canal.
CHAN OF INT comm:
PAR
INT x:
SEQ
input ? x
comm ! 2 * x
INT y:
SEQ
comm ? y
output ! y + 1
Aici avem 2 canale numite input si output care sunt legate la
alte procese sau poate chiar la lumea inconjuratoare. Presupunem
ca ele au fost declarate in alt loc. Aceasta portiune de program
foloseste 2 procese paralele, unul care inmulteste valoarea de
intrare cu 2, iar celalalt aduna 1 rezultatului si il trimite
pe canalul de iesire. Cele 2 procese comunica pe canalul comm.
Obs: Nu va nelinistiti. Acest exemplu nu este unul folositor.
Nu este decat o ilustrare. Ar fi fost mult mai simplu sa
inmultim cu 2 si sa adunam 1 intr-un singur proces SEQ,
sau chiar intr-o singura expresie. Dar mai tarziu, cand
vom sti mai multe despre Occam, vom vedea cat de folositoare
pot fi aceste lucruri.
S-a spus de mai multe ori ca un canal Occam este o legatura
unidirectionala intre 2 procese, dar a sosit momentul sa
vedem ce implica aceasta. Intr-un constructor PAR inseamna:
1) Doar 2 procese intr-un PAR pot folosi acelasi canal, unul ca
transmitator si altul ca receptor.
CHAN OF INT comm:
PAR
SEQ
comm ! 2
INT y:
SEQ
comm ? y
INT z:
SEQ
comm ? z -- ilegal, 2 procese receptioneaza
-- pe acelasi canal
2) Procesul transmitator nu poate decat transmite, iar cel
receptor nu poate decat receptiona.
CHAN OF INT comm:
PAR
SEQ
comm ! 2
INT y:
SEQ
comm ? y
comm ! y + 1 -- ilegal, input si output pe acelasi canal
Pentru o comunicatie bidirectionala intre 2 procese este nevoie
de 2 canale.
CHAN OF INT comm1, comm2:
PAR
INT x:
SEQ
comm1 ! 2
comm2 ? x
INT y:
SEQ
comm1 ? y
comm2 ! 3
Efectul acestui program este ca x va avea valoarea 3, iar
y va avea valoarea 2. Ordinea intrarilor si iesirilor in SEQ
este foarte importanta. Iata un exemplu in care se poate
ajunge la blocare daca scriem:
CHAN OF INT comm1, comm2:
PAR
INT x:
SEQ
comm2 ? x
comm1 ! 2
INT y;
SEQ
comm1 ? y
comm2 ! 3
De ce blocare? Pentru ca ambele SEQ-uri asteapta rabdatoare ca
intrarile se devina disponibile. Dar cum fiecare se asteapta pe
celalalt sa trimita, niciunul nu poate continua pentru a executa
iesirea. Seamana cu acele scene comice in care 2 oameni incearca
sa treaca printr-o usa ingusta si fiecare se invita reciproc sa
treaca, fara ca unul sa indrazneasca. Inversind intrarea cu iesirea
in oricare din cele 2 procese rezolva problema.
Atentie:
Secventiati-va programele astfel incat sa fiti siguri ca
nu exista situatii in care 2 procese paralele asteapta
fiecare iesiri secvential mai tarzii de la celelalt.
Aceasta este singura circumstanta in care trebuie sa va
ingijorati cand scrieti programe Occam.
Procese repetitive
Toate limbajele de programare ofera cateva metode de buclare, adica
de executie repetata a unor actiuni. In general se convine sa se
distinga 2 tipuri de repetitie: cu numar specificat de repetitii
sau cat timp se indeplineste o conditie. Occam are ambele tipuri
de repetitie. Primul, sau repetitie contorizata o sa-l studiem mai
tarziu. Al doilea tip se realizeaza cu ajutorul constructorului
WHILE care include un test cum ar fi de exemplu x < 0 sau fred = 100.
Procesul rezultat este executat cat timp testul este adevarat, sau
privit, altfel pana cand devine fals. De exemplu:
INT x:
SEQ
x := 0
WHILE x >= 0
SEQ
input ? x
output ! x
va continua sa citeasca valori de pe canalul input si sa le trimita
pe canalul output cat timp aceste valori nu sunt negative.
De fiecare data cand se termina procesul interior SEQ se va relua
procesul WHILE si deci se face din nou testul. Asta pana cand
testul va avea valoarea FALSE, cand procesul WHILE se termina.
Obs: Efectul acestuiproces este de a buffera (a stoca) o
singura valoare in drumul ei de la intrare la iesire.
Programele Occam sunt deseori scrise facind procesele
majore sa comunice intre ele prin canale si apoi inserind
procese simple ca acesta cu scopul de a buffera, sau
a filtra sau a transforma valorile trimise, aproape
ca si cand ar fi componente electrice si nu programe.
Valorile logice TRUE si FALSE pot fi folosite drept constante
in programele Occam oriunde poate fi folosit un test. Deci:
WHILE TRUE
INT x:
SEQ
input ? x
output ! x
se va executa la nesfarsit, pe cand
WHILE FALSE
INT x:
SEQ
input ? x
output ! x
se va termina imediat fara sa faca nici o operatie.
Procese conditionale
Pe langa repetitie, toate limbajele de programare trebuie sa
ofere programatorilor o metoda de a alege executia a diferite
lucruri in conformitate cu o conditie, adica cu rezultatul
unui test. In Occam o forma de alegeri conditionate este
oferita prin constructorul IF.
IF poate lua mai multe procese, fiecare avand un test plasat
inaintea lui, si continuind cu unul singur si anume cu primul
in ordinea in care sunt scrise al carui test este TRUE.
IF
x = 1
chan1 ! y
x = 2
chan2 ! y
In acest fragment de program (presupunem ca x, y, chan1, chan2
sunt declarate in alta parte), valoarea lui y va fi trimisa fie
pe canalul chan1 fie pe chan2 in functie de valoarea lui x.
Testele x = 1 si x = 2 sunt expresii booleene care sunt folosite
pentru alegerea executiei unei componente a lui IF. Partile
componente ale lui IF se numesc variante si sunt alcatuite
dintr-o expresie booleana si un proces.
Ce se intampla daca x = 3? Procesul IF va provoca blocarea
programului ca si cand am executa un STOP. Programul nu poate
continua decat daca se executa una din variante.
Obs: IF fara componente este un STOP.
PAR, SEQ fara componente = SKIP.
In multe situatii nu este acceptabil ca programul sa se blocheze
daca x nu are nici valoarea 1 nici 2. In aceste cazuri mai
introducem o varianta care se executa indiferent de valoarea lui x.
Aceasta se rezolva folosind TRUE:
IF
x = 1
chan1 ! y
x = 2
chan2 ! y
TRUE
chan3 ! y
Acum y va fi trimis pe canalul chan3 daca x nu este nici 1 nici 2.
Daca dorim sa nu se intample nimic daca x = 3:
IF
x = 1
chan1 ! y
x = 2
chan2 ! y
TRUE
SKIP
Iata un exemplu de utilitate a lui SKIP.
O metoda mai buna de scriere a exemplului de mai sus este:
IF
x = 1
chan1 ! y
x = 2
chan2 ! y
(x <> 1) AND (x<>2)
SKIP
Obs: Asta este o metoda mai buna deoarece este total neambigua.
Pentru simplicitate vom continua sa folosim in aceste
situatii TRUE, desi in programele reale ar trebui sa evitati.
Pentru constructia variantelor mai complexe, IF-urile pot fi
imbricate utilizind un IF in varianta unui alt IF:
IF
x = 1
chan1 ! y
x = 2
IF
y = 1
chan2 ! y
TRUE
chan3 ! y
TRUE
SKIP
In acest exemplu chan3 este folosit pentru iesire daca x este 2,
iar y este diferit de 1.
Procese alternative
In Occam, variantele au o dimensiune suplimentara in raport cu
limbajele de programare ordinare. Tocmai am vazut cum putem face
alegeri in functie de valorile variabilelor utilizind
constructorul IF. Dar putem face alegeri si in functie de starea
canalelor. Acestea se pot face utilizind constructorul ALT,
al carui nume este o prescurtare de la alternativa.
Daca IF ne permite alegerea proceselor in functie de starea
variabilelor, ALT ne permite alegerea proceselor in functie
de starea canalelor.
La fel ca IF, ALT aduna mai multe componente intr-o constructie
unica, dar componentele lui ALT, numite alternative sunt mai
complicate decat variantele IF-ului.
Cel mai simplu fel de ALT are fiecare alternativa un proces
de intrare urmat de un proces pentru executie. ALT verifica
toate procesele de intrare si executa procesul asociat cu
prima intrare disponibila. In acest fel, ALT se comporata
ca o cursa intre canale dintre care se executa doar invingatorul:
CHAN OF INT chan1, chan2, chan3:
INT x:
ALT
chan1 ? x
...primul proces
chan2 ? x
...al doilea proces
chan3 ? x
...al treilea proces
Daca chan2 este primul canal cu intrarea disponibila, atunci
se executa doar al doilea proces.
Aici alegerea se face pe dimensiunea temporala, intrarile
provocind oprirea tempoarara a programului pana cand una dintre
ele devine disponibila.
O alternativa poate incepe cu un test pe langa o intrare, la fel
cu testele IF. In acest caz, procesele asociate nu se executa decat
daca intrarea lui este prima disponibila si in plus daca testul
este TRUE. Occam face acest lucru mai usor de retinut folosind
semnul &:
CHAN OF INT chan1, chan2, chan3:
INT x:
ALT
(y < 0) & chan1 ? x
...primul proces
(y = 0) & chan2 ? x
...al doilea proces
(y > 0) & chan3 ? x
...al treilea proces
Daca de exemplu y este 3 si chan3 este primul canal disponibil,
atunci se va executa al treilea proces.
Obs: Ca IF, ALT este un STOP daca nu are alternative.
Tot ca IF, ALT poate fi imbricat.
ALT este un costructor foarte puternic. Permite conectarea unei
retele complexe de canale intr-o maniera eleganta.
Datorita puterii lui si deoarece nu are nimic analog in limbajele
conventionale, ALT este de departe cel mai greu de explicat si de
inteles constructor din Occam. Din fericire am vazut deja suficient
Occam ca sa fim in stare sa abordam probleme mai serioase care
pot clarifica folosirea lui.
Un program pentru un controlor simplu
Sa presupunem ca scriem un program pentru controlul unui
amplificator de sunet. Ca majoritatea accesoriilor moderne,
are controale digitale in loc de butoane rotative.
Pentru controlul volumului de sunet amplificatorul are 2 butoane:
louder (mai tare) si softer (mai incet). Apasind pe louder
crestem volumul cu o unitate, iar apasarea pe softer scade volumul
cu o unitate.
Avem 2 canale Occam denumite deasemeni louder si softer care
produc o intrare de fiecare data cand apasam pe un buton. Mai
avem un al treilea canal numit amplifier care trimite o valoare
sectuinii de amplificare unde un cip de control seteaza volumul
la aceasta valoare.
Procesele pentru cresterea si descresterea volumului sunt:
SEQ
volume := volume + 1
amplif ! volume
SEQ
volume := volume - 1
amplif ! volume
Programul decide care buton a fost apasat ultimul si deci ce actiune
sa execute. Combinind cele 2 procese intr-un ALT, obtinem:
ALT
louder ? any
...creste volum
softer ? any
...descreste volum
Nu ne intereseaza valoarea trimisa, ci doar evenimentul. Asa ca
vom declara o variabila any doar ca sa putem executa procesele
de intrare. In plus mai trebuie ca procesul sa se execute continuu.
INT volume, any:
SEQ
volume := 0
amplif ! volume
WHILE TRUE
ALT
louder ? any
SEQ
volume := volume + 1
amplif ! volume
softer ? any
SEQ
volume := volume - 1
amplif ! volume
Ne deranjeaza WHILE TRUE. Asta inseamna ca nu se termina niciodata.
Mai consideram un buton OFF si un canal off pentru oprirea
programului.
BOOL activ:
INT volume, any:
SEQ
activ := TRUE
volume := 0
amplif ! volume
WHILE activ
ALT
louder ? any
SEQ
volume := volume + 1
amplif ! volume
softer ? any
SEQ
volume := volume - 1
amplif ! volume
off ? any
activ := FALSE
Acum programul se termina frumos cand se apasa butonul OFF.
Putem face verificarea limitelor volumului.
VAL maximum IS 100:
VAL minimum IS 0:
BOOL activ:
INT volume, any:
SEQ
activ := TRUE
volume := minimum
amplif ! volume
WHILE activ
ALT
(volume < maximum) & louder ? any
SEQ
volume := volume + 1
amplif ! volume
(volume > minimum) & softer ? any
SEQ
volume := volume - 1
amplif ! volume
off ? any
activ := FALSE
Obs: 1) nu au fost declarate canalele louder, softer, off, amplif
pentru ca sunt conectate la hardware si nu la procese occam.
2) intr-un program real de control, mai multe lucruri pot
fi controlate in paralel. Procesele care fac acest lucru
pot fi combinate intr-un PAR.
Aritmetica in Occam
Pana acum nu am discutat ce operatori aritmetici sunt disponibili
in Occam, desi am considerat natural sa aiba adunare, scadere si
inmultire.
Operatiile artimetice de baza sunt:
x + y adunarea lui x cu y
x - y scaderea lui x cu y
x * y inmultirea lui x cu y
x / y catul impartirii lui x la y
x REM y restul impartirii lui x la y
Aceste operatii pot fi facute asupra numerelor intregi sau reale.
Toti operatorii au aceeasi prioritate asa ca in Occam trebuie
folosite paranteze. De exemplu:
(2+3)*(4+5) -- 45
2+(3*(4+5)) -- 29
(2+(3*4))+5 -- 19
2+3*4+5 -- ilegal
Testele sunt de asemenea operatori asa ca pentru a evita
interpretarea incorecta trebuie sa folosim paranteze. De exemplu:
fred = (2+jim) -- legal
(fred+jane) > jim -- legal
fred +jane > jim -- ilegal
fred + (jane > jim) -- ilegal (mixarea tipurilor)
Doar pentru intregi exista un set de operatori modulo. Aritmetica
modulo se ocupa cu sistemele de numerotatie in care exista un
domeniu limitat de numere. In mod normal, preferam sa credem ca
intotdeauna exista un numar mai mare decat oricare numar la care
ne gandim.
Ca un exemplu, in aritmetica modulo 8 fara semn, se permit doar
numere de la 0 la 7. Adica 7 + 1 modulo 8 este 0, (5 + 7) modulo 8
este 4. Un exemplu de zi cu zi de aritmetica modulo este aritmetica
orelor ceasului (modulo 12), in care daca adunam 3 ore la ora 11,
obtinem ora 2.
Aritmetica modulo este importanta in calculatoare deoarece acestea
lucreaza mereu cu numere limitate de dimensiunea memoriei folosite
pentru stocarea lor. De exemplu, cel mai mare intreg cu semn care
poate fi reprezentat pe 16 biti este 32767.
Occam are urmatorii operatori modulo
PLUS, MINUS, TIMES
care corespund adunarii, scaderii si respectiv inmultirii modulo
2**(numarul de biti intr-un INT).
Pentru valorile de adevar, Occam are urmatorii operatori booleeni:
NOT, AND, OR
Conversii de tipuri
Cateodata este mai comod sa convertim un tip in altul; de exemplu
nu mai trebuie sa declaram variabile suplimentare pentru aceeasi
valoare. Conversiile de tipuri trebuie folosite cat mai rar,
deoarece scopul tipurilor este prevenirea folosirii incorecte a
valorilor.
Daca variabila number este declarata ca INT, iar digit BYTE, putem
sa le adunam in felul urmator:
INT number:
BYTE digit:
number := (number * 10) + (INT digit)
Conversia inversa de la INT la BYTE este legala doar daca valoarea
este in domeniul BYTE (0-255). De exemplu, pentru a trimite un
numar intre 0 si 9 ca un caracter, putem scrie:
output ! BYTE (number + (INT '0'))
Valorile de tip BOOL pot fi convertite la tip INT sau BYTE si invers
folosind urmatoarele definitii:
INT TRUE sau BYTE TRUE este 1
INT FALSE sau BYTE FALSE este 0
BOOL 1 este TRUE
BOOL 0 este FALSE
deci daca valoarea lui active este FALSE, INT active este 0.
Operatori la nivel de bit
Pentru a permite oparatii la nivel scazut asupra bitilor
individuali ai unei valori, Occam ofera urmatirii operatori
la nivel de bit:
~ not
/\ and
\/ or
>< xor
>> logic shift left
<< logic shift right
Acesti operatori actioneaza doar asupra intregilor.
Constantele hexa sunt precedate de #
#FE = 254
Abrevieri
Notatia pe care am vazut-o mai devreme pentru definirea
constantelor (VAL maximum IS 1000) nu este decat o particularizare
a unui mecanism mult mai puternic numit abreviere.
Abrevierile pot fi folosite pentru a da un nume oricarei expresii
din Occam. De exemplu:
VAL exp IS ((x + y) / (fred * 128)):
defineste exp ca fiind abreviere expresiei mult mai complexe din
dreapta. Faptul ca aceasta specificatie se termina cu : ne spune
ca abrevierile sunt locale, ca si celelalte obiecte Occam, iar
domaniul lor de valabilitate este procesul care urmeaza definirii
lor.
Expresiile abreviate pot contine variabile in partea dreapta, dar
aceste variabile trebuie sa ramana constante pe intreg domeniul
abrevierii. Daca aceste variabile sunt schimbate (prin atribuire
sau intrare de pe un canal) atunci vom avea erori la compilare.
In concluzie o expresie abreviata se comporta ca o constanta.
Forma completa a abrevierii unei expresii contine si tipul ei
inainte de nume:
VAL INT exp IS ((x + y) / (fred * 128)):
dar acesta poate fi omis deoarece Occam-ul isi da seama de tipul
abrevierii din tipurile valorilor din partea dreapta.
Occam priveste numerele mai mici decat 255 ca intregi INT:
VAL Esc IS 27:
Daca vrem altfel, trebuie sa specificam tipul numrului in felul
urmator:
VAL Esc IS 27 (BYTE):
Proceduri
O procedura este un proces cu un nume si acest nume poate fi folosit
pentru a reprezenta procedura in alte procese. Pentru a defini
o procedura se foloseste cuvantul cheie PROC urmat de numele
procedurii urmat de un proces care se cheama corpul procedurii.
Corpul procedurii este executat de fiecare data cand numele lui
este intalnit in program. Aparitia numelui procedurii in program se
cheama instanta a procedurii.
Definirea unei proceduri arata in felul urmator:
PROC delay() -- headerul procedurii
VAL interval IS 1000: --
INT n: --
SEQ -- corpul procedurii
n := interval --
WHILE n > 0 --
n := n - 1 --
: --
INT y: -- procesul principal
SEQ -- incepe aici
input1 ? y
delay() -- instanta
output1 ! y
delay() -- instanta
input2 ? y
delay() -- instanta
output2 ! y
Tot ce face aceasta procedura este sa numere invers de la 1000
la 0, in acest fel introducind o intarziere in program. Vom
studia mai tarziu metoda potrivita de a introduce intarzieri in
Occam utilizand timere. Cele 2 paranteze goale ce urmeaza numelui
delay () arata ca aceasta procedura nu are parametri.
Observati cum la fel cu toate specificatiile si aceasta este atasata
procesului care urmeaza definirii ei prin cele :. Acest lucru ne
spune ca numele procedurii se supune acelorasi reguli ca si numele
variabilelor sau celelalte nume: procedura este cunoscuta doar in
cadrul procesului care urmeaza imediat. In Occam cele : care termina
definirea unei proceduri sunt puse pe o linie noua exact sub P-ul
din headerul procedurii.
Executia acestui program incepe in procesul principal; citeste valori
de pe 2 canale de intrare (declarata in alta parte) si este intarziat
o vreme inainte sa trimita valorile pe canalele de iesire. La fiecare
intalnire a unei instante a procedurii delay, aceasta este executata
ca si cand corpul procedurii ar fi fost substituit numelui:
INT y:
SEQ
input1 ? y
VAL interval IS 1000:
INT n:
SEQ
n := interval
WHILE n > 0
n := n-1
output1 ! y
VAL interval IS 1000:
INT n:
SEQ
n := interval
WHILE n > 0
n := n-1
input2 ? y
VAL interval IS 1000:
INT n:
SEQ
n := interval
WHILE n > 0
n := n-1
output2 ! y
In acest fel PROC ne ofera o prescurtare; numele delay este nu numai
mai scurt decat codul pe care il inlocuieste, dar aici il inlocuieste
de 3 ori. Astfel, procesul principal devine mai compact si mai
citibil deoarece numele delay ne da o idee despre ce face.
Nota: O procedura poate fi compilata fie prin substituirea corpului
ei, fie ca o subrutina.
Utilizarea procedurilor aduce multe alte beneficii. Ele ofera o
metoda de a structura programele. Prin spargerea unui program in
parti mai mici (factorizarea problemei) si apoi scrierea acestor
parti ca proceduri, logica intregului program este mai clara si
mai usor de urmarit.
De obicei, procedurile se folosesc mai mult decat o data, in acest
fel reducindu-se dimensiunea programelor, iar unele dintre ele
fot fi folosite in mai multe programe.
Programele factorizate cu grija sunt usor de modificat, depanat
si intretinut deoarece modificarea unei singure declaratii de
procedura este suficienta pentru ca schimbarile sa fie simtite
automat peste tot unde procedura este folosita.
Parametri
Procedurile pot fi si mai folositoare prin introducerea parametrilor,
ceea ce permite trimiterea de valori diferite instantelor diferite.
In exemplul delay de mai sus, durata intarzierii era fixata la 1000
(prin abreviarea VAL interval IS 1000) si nu putea fi schimbata decat
prin editarea programului. O metoda mai flexibila ar fi sa pasam
durata de intarziere ca parametru al procedurii.
PROC delay(VAL INT interval)
INT n:
SEQ
n := interval
WHILE n > 0
n := n - 1
:
INT y:
SEQ
input1 ? y
delay(1000)
output1 ! y
delay(2000)
input2 ? y
delay(500)
output2 ! y
Numele interval se cheama parametru formal.
Parametri formali pot fi de orice tip inclusiv CHAN, iar specificare
tipului parametrilor formali se face in headerul procedurii. Occam
nu va poate citi gandurile asa ca nu-si poate da seama ce tip are
un parametru formal daca nu ii spuneti.
O procedura poate avea oricati parametri formali. Acestia trebuie
separati prin virgula in headerul procedurii. Parametrii actuali
ai unei instante sunt deasemenea separati prin virgula. Numarul de
parametri actuali trebuie sa fie egal cu numarul de parametri
formali, iar corespondenta se face prin pozitia parametrilor:
primul parametru actual este pentru primul parametru formal ...
PROC volumul.cutiei (VAL INT lungime, latime, adancime)
... corp -- definitie
:
volumul.cutiei (24, 16, 20) -- instanta
Procedurile cu parametri aduc o prescurtare si mai puternica
deoarece acum putem folosi aceeasi procedura in locuri diferite
cu valori diferite.
Parametrii actuali ai procedurilor nu trebuie sa fie neaparat
constante. In procedura delay, parametru actual poate fi orice
variabila de tipul INT, de exemplu delay(x).
Transmiterea parametrilor
In Occam transmiterea parametrilor se face prin referinta.
Orice modificare asupra parametrului formal se face si asupra
parametrului actual.
PROC decrement (INT number)
number := number - 1
:
Daca folosim decrement (x) ca o instanta a procedurii, valoarea lui
x va fi decrementata dupa executia procedurii.
Atentie:
Acest comporatment difera fata de cel din alte limbaje
de larga raspandire. Conventia utilizata in C sau Pascal
este de transfer prin valoare, ceea ce are ca efect
evaluarea parametrului actual si utilizarea rezultatului
ca valoare initala pentru parametrul formal. In consecinta,
putem modifica parametrul formal fara ca acest lucru sa
afecteza parametrul actual. Conventia folosita in Occam
este mai apropiata de transmiterea prin referinta a
parametrilor (in Pascal VAR).
Cateodata se doresta ca o procedura sa nu modifice parametrii
actuali. Pentru aceasta se poate copia valoarea parametrilor formali
in niste variabile locale procedurii care sa fie apoi folosite.
Daca se foloseste doar valoarea initiala a parametrilor formali
si acestia nu sunt modificati prin atribuire sau intrare, atunci
putem construi un program mult mai eficient specificind explicit
transferul prin valoare. Aceasta se face utilizind VAL:
PROC delay (VAL INT limit)
INT n:
SEQ
n := 0
WHILE n < limit
n := n + 1
:
Cand folosim VAL, parametrul formal poate fi privit ca o constanta.
Putem rescrie programul pentru controlul volumului, folosind de data
aceasta o procedura:
VAL step.up IS 1:
VAL step.down IS -1:
BOOL active:
INT volume, any:
PROC change.volume (VAL INT step)
SEQ
volume := volume + step
amplifier ! volume
:
SEQ
volume := 0
amplifier ! volume
active := TRUE
WHILE active
ALT
louder ? any
change.volume (step.up)
softer ? any
change.volume (step.down)
off ? any
active := false
Observati cum folosim o singura procedura atat pentru cresterea
cat si pentru micsorarea volumului.
O alta observatie este aceea ca in aceasta procedura se foloseste
variabila volume desi ea nu este declarata nici ca variabila
locala, nici ca parametru formal. Variabila volume se numeste
variabila libera in raport cu procedura change.volume si are
proprietatea ca isi pastreaza valoarea de la un apel la altul
al procedurii. Volume trebuie declarata undeva inainte de declararea
procedurii.
O variabila este libera in raport cu o procedura atunci cand
procedura este definita in domeniul de valabilitate al variabilei.
Functii
Majoritatea limbajelor conventionale suporta functii. Pe scurt,
o functie este un proces care intoarce o valoare si care poate fi
folosita in expresii. Multe functii sunt matematice si intorc
de exemplu sinusul sau cosinusul argumentului. Occam ofera tipul
de functii in care poti sa ai cea mai mare incredere.
In Occam o functie da un nume unui proces special care intoarce
o valoare. Acest tip de proces se numeste proces valoare. Functiile
Occam au avantajul de a fi lipsite de efecte laterale. Practic acest
lucru inseamna ca Occam este foarte strict in legatura cu folosirea
functiilor. Marele avantej este ca atunci cand folosim o functie
putem fi siguri ca ea nu va avea efecte laterale asupra oricarei
altei parti din program. Multe din bug-urile care apar misterios
in programe se datoreaza faptului ca nu putem avea aceleasi garantii
in alte limbaje.
Forma generala a definitiei unei functii este:
type FUNTION nume ( {parametrii formali} )
declaratii:
VALOF
proces
RESULT expresie
:
De exemplu:
INT FUNCTION max (VAL INT a, b)
INT answer:
VALOF
SEQ
IF
a >= b
answer := a
b > a
answer := b
RESULT answer
:
O functie poate apare oriunde poate apare o expresie:
x := max(a, b)
x := max(a, b) * 2
Observati specificatorul de tip ce precede cuvantul cheie FUNCTION
din headerul functiei.
Regulile pentru construirea functiilor sunt foarte stricte:
- nu pot folosi decat parametri VAL (transmisi prin valoare)
- nu pot face comunicatii
- nu pot face atribuiri variabilelor nelocale
- nu pot folosi ALT
- nu pot folosi PAR
- nu pot apela decat proceduri care definite in domeniul functiei
si care respecta aceste restrictii.
5. Matrici in Occam
O matrice este un grup de obiecte de acelasi tip unite intr-un
singur obiect cu un nume. Fiecare obiect din matrice poate fi
referit individual prin specificarea numarului pozitiei lui in
matrice. Acest numar se numeste index, iar obiectul individual
componenta a matricii.
In Occam variabilele matrice se declara asemanator cu variabilele
singulare cu deosebirea ca numarul elementelor este specificat intre
paranteze drepte inainte de specificatorul de tip:
[20]INT fred: -- o matrice fred de 20 de intregi
[100]CHAN OF INT switchboard: -- o matrice de 100 de canale
[12]BOOL jury: -- o matrice jury de 12 valori booleene
Nota: In Occam dimensiunea matricilor trebuie cunoscuta la
compilare.
Selectia unei componente a unei matrici se face specificind numele
matricii urmat de indexul componentei intre paranteze drepte:
fred[0] -- primul intreg din fred
switchboard[20] -- al 21-lea canal din switchboard
jury[11] -- ultimul element din jury
Atentie:
Folosirea unui index mai mare decat dimensiunea matricii
va cauza o eroare insa unde si cum se va raporta aceasta
eroare este dependenta de implementare.
Componentele unei matrici se comporta ca niste variabile obisnuite
de acelasi tip:
chan1 ? fred[15] -- citire din canalul chan1 in a 16-a
-- componenta a lui fred
jury[4] := TRUE -- a 5-a componenta a lui jury ia valoarea
-- TRUE
switchboard[99] ! fred[1] -- output valoarea celei de a 2-a componente
-- a lui fred pe ultimul canal din
-- switchboard
Exemplu: un proces care citeste valori de pe un canal (declarat in
alt proces) in componente succesive ale unei matrici:
INT index:
[100]INT array:
SEQ
index := 0
WHILE index < 100
SEQ
chan1 ? arary[index]
index := index + 1
Se pot face atribuiri, iesiri sau intrari cu matrici intregi, cu
conditia ca in cazul atribuirii matricile sa fie de acelasi tip,
iar in cazul intrarilor sau iesirilor, protocolul canalului sa
fie de acelasi tip cu matricea.
Tipul matricilor
Tipul matricilor reflecta atat dimensiunea cat si tipul componentelor
Tipul lui fred este [20]INT.
Intreaga matrice fred poate fi trimisa altui proces astfel:
PAR
comm ! fred
...alte procese
comm ? jane
daca jane este o matrice de tipul [20]INT, iar canalul comm trebuie
declarat:
CHAN OF [20]INT comm:
Matricile pot fi folosite si ca parametri pentru proceduri.
In Occam nu trebuie specificata dimensiunea matricii parametru
la declararea procedurii, adica poate fi transmisa orice matrice
cu tipul componentelor corect.
Aceasta este o proprietate puternica ce permite unei proceduri
manipularea matricilor de dimensiuni variabile. Pentru a afla
cat de mare este o matrice, in Occam exista operatorul SIZE.
Exemplu:
PROC read.in (CHAN OF INT input, []INT array)
INT index:
SEQ
index := 0
WHILE index < (SIZE array)
SEQ
input ? array[index]
index := index + 1
:
Acesata procedura poate fi apelata de exemplu:
read.in (switchboard[12], fred)
SIZE intoarce totdeauna o valoare INT.
Segmente de matrici
Un segment de matrice este exprimat astfel:
[matrice FROM start FOR nrelem]
start si nrelem trebuie sa fie de tip INT.
Un segment de matrice consta din nrelem elemente consecutive
ale matricii, incepand cu matrice[start].
[fred FROM 10 FOR 5] este o matrice compusa din elementele
fred[10],...,fred[14].
[fred FROM 0 FOR 20] este echivalenta cu fred.
Segmentele de matrici pot fi trimise pe canale, pot primi
valori de pe canale sau pot fi modificate prin atribuire.
Exemple:
[fred FROM 10 FOR 5] := [fred FROM 1 FOR 5]
[fred FROM 10 FOR 5] := jane
unde jane trebuie declarata ca [5]INT.
Tabele
Occam ofera o metoda puternica de generare a valorilor matricilor,
numita tabela. Daca x este o variabila INT care are valoarea 10,
atunci tabela:
[x, x+1, x+2, x+3]
genereaza o matrice de tipul [4]INT cu valoarea [10, 11, 12, 13].
Aceasta matrice poate fi asignata unei variabile:
INT x:
[4]INT xarray:
SEQ
...
xarray := [x, x+1, x+2, x+3]
sau poate fi indexata direct:
chan3 ! [x, x+1, x+2, x+3][2]
sau poate fi abreviata:
INT x:
...
VAL xarray IS [x, x+1, x+2, x+3]:
SEQ
...
chan3 ! xarray[2]
Mai multe despre abrevieri
Declaratia:
f IS [fred FROM 10 FOR 5]:
permite tratarea lui f ca o matrice de 5 elemente care poate
fi indexata de la 0 la 4. Deci f[2] se refera la fred[12].
Observati ca nu se foloseste VAL ca in cazul abrevierii unei
constante. Noi aici nu dam un nume unei valori. O abreviere
pentru o matrice, un element de matrice, segment de matrice sau
pentru o singura variabila se comporta ca obiectul in sine. Cu
alte cuvinte se permite schimbarea valorii obiectului referit
de abreviere.
Exemplul de atribuire poate fi rescris mult mai succint:
f := jane
Ceea ce nu este permis in cazul unei abrevieri pentru un segment
de matrice sau element de matrice este modificarea indexului care
defineste abrevierea. De exemplu:
line IS switchboard[i]:
nu avem voie sa modificam pe i in domeniul lui line.
In cpncluzie, o abreviere pentru un segment de matrice sau pentru
un element se comporta ca elementul in sine cu un index constant
in domeniul abrevierii.
O matrice constanta poate fi declarata ca o abreviere:
VAL power2 IS [1,2,4,8,16,32,64,128]:
power2[3] este 8.
Matrici multidimensionale
Occam suporta matrici cu oricate dimensiuni si acestea se declara
intr-o maniera consistenta cu ce am vazut pana acum.
O tabla de sah poate fi declarata:
[8][8]BYTE chessboard:
chessboard[3][4] este elementul de pe linia 3, colana 4.
chessboard[5] este toata linia 5.
Daca declaram constantele
VAL empty IS 0 (BYTE):
VAL pion.negru IS 1 (BYTE):
VAL nebun.negru IS 2 (BYTE):
...
atunci un program poate initializa tabla:
chessboard[0][0] := turn.negru
iar o mutare poate fi
chessboard[3][4] := chessboard[4][5]
chessboard[4][5] := empty
O matrice constanta bidimensionala poate fi declarata ca o abreviere:
VAL curba IS [[0,0],[1,1],[2,4],[3,9],[4,16]]:
valoarea lui curba[3] este [3,9], iar
valoarea lui curba[3][1] este [9].
6. Comunicatia prin canale
Programarea in Occam este preocupata in mod deosebit cu comunicatia
intre procese, iar canalele sunt principalele mijloace de realizare
a comunicatiei. Este important deci sa putem trimite date de orice
tip prin canale. Am vazut ca la fel ca orice variabila si canalele
trebuie sa aiba un tip.
La fel de important este sa putm trimite structuri de date, altfel
am fi nevoiti sa folosim mai multe canale de tipuri diferite doar
pentru o singura comunicatie. O solutie ar fi sa nu mai verificam
tipul datelor care se transfera pe canale, dar aceasta ar insemna
sa renuntam la avantajele folosirii compilatorului ca scula de
a crea programe eficiente si sigure.
Occam ofera mai multe posibilitati de a grupa tipuri de date si
a crea un protocol de comunicatie. Protocolul unui canal este
specificat la declararea canalului:
CHAN OF protocol nume:
La fiecare trimitere sau receptie pe un canal, ceea ce trimitem sau
receptionam trebuie sa fie compatibil cu protocolul canalului, adica
sa se potriveasca atat la numarul obiectelor cat si la tipul fiecarui
obiect.
Protocoale simple
Cel mai simplu fel de protocol este cel care consta dintr-un tip
primitiv de date:
CHAN OF INT comm: -- comm este un canal de intregi
CHAN OF [20]INT x: -- x este un canal de matrici de 20 de intregi
Trimiterea si citirea de pe canale cu protocoale simple sunt legale
doar daca variabila de trimitere sau cea de receptie este de acelasi
tip cu protocolul canalului.
Exemplu de program incorect:
[30]INT x:
CHAN OF [20]INT y:
SEQ
y ? x -- eroare, x si y sunt de tipuri diferite.
Numirea protocoalelor
Protocoalele pot deveni complexe cum ar fi de exemplu o secventa de
protocoale mai simple. Protocoalelor li se pot asocia nume:
PROTOCOL Word IS INT:
PROTOCOL Pair IS INT; INT:
...
CHAN OF Word words:
CHAN OF Pair pairs:
Protocoale secventiale
Un protocol secvential specifica faptul ca o secventa de valori,
posibil de tipuri diferite, poate fi trimisa pe canal.
Exemplu de definire a protocolului si de declarare a canalului:
PROTOCOL Mesaj IS BYTE; INT; INT:
...
CHAN OF Mesaj comm:
comm poate comunica mesaje formate dintr-un BYTE urmat de 2 intregi.
Mesajul
40(BYTE); 999; 505
este corect, dar
999; 505; 40(BYTE)
este incorect pentru ca nu incepe cu un BYTE, iar
40 (BYTE); 999; 505; 324
este deasemenea incorect (prea multe valori).
In general un protocol secvential este o lista de tipuri separate de ;
Exemplu de folosire a protocolului secvential:
comm ! 40(BYTE); 999; 505
Similar, input:
comm ? x; y; z
Un program care trimite structui de numere pe canal:
PROTOCOL Mesaj IS BYTE; INT; INT :
CHAN OF Mesaj comm:
PAR
SEQ
...procese
comm ! 40(BYTE); 999; 505
BYTE x:
INT y, z:
SEQ
comm ? x: y: z
...alte procese
Protocol matrice cu lungime variabila
Un caz particular de protocol este asa de des folosit incat s-a
creat un tip special pentru el. Pentru trimiterea de matrici cu
dimensiune variabila, acest protocol are 2 campuri: mai intai
dimensiunea matricii si apoi matricea. Acest protocol se defineste:
type1::type2
unde type1 este tipul dimensiunii matricii, oar type2 este tipul
componentelor matricii.
Exemplu de canal care poate transporta matrici de intregi de
orice dimensiune :
CHAN OF INT::[]INT comm:
sau daca stim ca matricile nu au mai mult de 256 elemente:
CHAN OF BYTE::[]INT comm:
Urmatorul proces trimite 2 matrici de intregi de dimensiuni
diferite pe acelasi canal:
CHAN OF INT::[]INT comm:
[20]INT fred:
[30]INT jane:
SEQ
comm ! 20::fred
comm ! 30::jane
Procesul de citire corespunzator:
INT size:
[256]INT array:
SEQ
comm ? size::array
...
comm ? size::array
Occam trateaza protocolul matrice de dimensiune variabila ca un
protocol simplu. De exemplu, protocolul:
PROTOCOL Doubles IS BYTE::[]BYTE; BYTE::[]BYTE:
permite trimiterea pe un canal a perechilor de matrici de octeti.
Protocol cu variante
Protocoalele secventiale ne permit sa specificam formatul mesajelor
ce pot fi transmise intre 2 procese pe un canal. Deseori insa este
de preferat ca un singur canal sa poata transporta mesaje de tipuri
diferite. Aceasta este posibil utilizind protocolul cu variante.
Un protocol cu variante este un set de protocoale diferite oricare
putind fi folosit pentru comunicarea pe canal. Protocoalele se
identifica cu ajutorul unor etichete. Protocoalele etichetate sunt
protocoale ale caror prima componenta este un nume unic care le
identifica. Setul de protocoale etichetate sunt grupate intr-o
constructie CASE. Protocoalele cu variante trebuie intotdeauna
numite printr-o definire de protocol. Exemplu:
PROTOCOL Mesaj
CASE
a; INT
b; BYTE; INT
c; BYTE; BYTE
:
a, b si c sunt etichete. Declararea unui canal de acest tip:
CHAN OF Mesaj comm:
comm poate transporta mesaje in varianta a, b, sau c.
Cand citim de pe un canal cu variante trebuie sa stim ce fel de
mesaj s-a trimis de fapt. Acesta este si scopul etichetelor.
Nu putem sa facem o intrare simpla de pe un canal cu variante
pentru ca nu stim ce variabila sa folosim. De exemplu:
INT x:
SEQ
comm ? x
ar fi compatibila cu mesaje in varianta a, dar nu si cu mesajele
in variantele b sau c.
Citirea cu CASE
Pentru a face o intrare de pe un canal cu variante, Occam ofera
o intrare cu variante. Exemplu:
PROTOCOL Mesaj
CASE
a; INT; INT
b; BYTE::[]BYTE
:
CHAN OF Mesaj comm:
PAR
INT x, y:
BYTE size:
[256]BYTE v:
comm ? CASE
a; x; y -- citire in varianta a
...proces
b; size::v -- citire in varianta b
...proces
Celelalte procese din PAR pot trimite mesaje astfel:
comm ! a; 100; 250
sau
comm ! b; 5::"Hello"
O citire cu CASE poate fi folosita intr-un ALT
INT x, y:
BYTE size :
[256]BYTE v:
ALT
comm ? CASE
a; x; y
...proces
b; size:: v
...proces
...alte alternative
Tipuri doar-eticheta
Uneori este util sa declaram canale care accepta numai etichete.
PROTOCOL Tags
CASE
one
two
three
:
CHAN OF Tags signal:
signal transporta una din cele 3 etichete one, two sau three
signal ? CASE
one
...actiunea 1
two
...actiunea 2
three
...actiunea 3
Aceasta metoda este mult mai buna decat ALT cu citire de pe mai
multe canale fara se ne intereseza continutul canalului.
Exemplu:
PROTOCOL Command
CASE
play
dance
quit
:
CHAN OF Command signal:
BOOL going:
...
SEQ
going := TRUE
WHILE going
signal ? CASE
play
...hamlet
dance
...tango
quit
going := FALSE
Un alt proces care vrea sa trimita semnalul quit va proceda astfel :
signal ! quit
Aceasta este o metoda mult mai sigura decat cea cu any. Compilatorul
poate detecta orice incercare de a trimite alte valori decat play,
dance sau quit pe canalul signal.
7. Caractere si stringuri
Occam foloseste codul ASCII cu un subset garantat.
Caracterele se scriu intre apostrofuri:
'a'
Aceste constante sunt translatate intr-un BYTE de Occam.
Stringurile se scriu intre ghilimele:
"Hello"
si sunt translatate in matrici [n]BYTE. Echivalent:
['H', 'e', 'l', 'l', 'o']
Caracterele neafisabile se scriu cu un asterix inainte :
*t tab
*n new-line
*numarhexa
Stringurile constante pot fi atribuite matricilor si manilpulate
in diferite feluri. Caracterele pot fi selectate din stringuri
prin indexare obisnuita, sau se pot selecta substringuri ca
segmente de matrici.
Exemplu de functie care intoarce un pointer la prima aparitie
a unui caracter intr-un sir.
INT FUNCTION find.char (VAL BYTE char, VAL []BYTE string)
INT index:
VALOF
SEQ
index := 0
WHILE (index < (SIZE string)) AND
char <> string [index])
index := index + 1
RESULT index
:
Apelul de functie:
c := find.char ('a', "carte")
are ca efect schimbarea valorii lui c la 1, deoarece indexul literei
'a' in "stringul" carte este 1. Observati ca daca stringul nu contine
caracterul specificat, functia intoarce lungimea strngului.
Trimiterea stringurilor
Cum stringurile sunt de obicei matrici de octeti, trimiterea lor
pa canale se poate face folosind protocolul matrice cu dimensiune
variabila:
PROTOCOL String IS BYTE::[]BYTE:
CHAN OF String comm:
PAR
SEQ
...
VAL mesaj IS "England expects":
comm ! BYTE(SIZE mesaj)::mesaj
...
[80]BYTE string:
BYTE size:
SEQ
comm ? size:: string
...
Stringurile pot fi trimise si caracter cu caracter:
PROC output.string (CHAN OF BYTE out, []BYTE mesaj)
INT i:
SEQ
i := 0
WHILE i < (SIZE mesaj)
SEQ
out ! mesaj[i]
i := i + 1
:
Daca dorim sa trimitem doar litere mari, atunci fiecare caracter
trebuie inmultit pe biti (SI logic pe biti) cu #DF.
PROC output.ucstring (CHAN OF BYTE out, []BYTE mesaj)
INT i:
SEQ
i := 0
WHILE i < (SIZE mesaj)
SEQ
out ! mesaj[i] /\ #FD
i := i + 1
:
Deoarece folosim trmiterea caracter cu caracter pe un canal de tipul
CHAN OF BYTE, procesul de receptie nu are de unde sa stie cand se
termina transmisia.
Citirea stringurilor
O solutie este sa trimitem mai intai lungimea stringului.
Alta este sa folosim un caracter special pe post de terminator.
PROC input.string (CHAN OF BYTE in, []BYTE mesaj)
INT i:
SEQ
i := 0
WHILE mesaj[i] <> end.of.file.char
SEQ
in ? mesaj[i]
i := i + 1
Rutina de trimitere va trebui sa trimita la sfarsit:
out ! end.of.file.char
8. Replicatori
Una dintre cele mai puternice caracteristici ale Occam-ului este
aceea ca permite pe langa matrici de date si de canale si
constructia de matrici de procese.
Matricile de procese se construiesc cu replicatori. Forma generala
a unui replicator este:
REP index=base FOR count
unde REP poate fi SEQ, PAR, ALT sau IF.
Nota: daca count este 0, atunci SEQ si PAR fara componente
se comporta ca un SKIP, iar ALT si IF ca un STOP.
SEQ replicat
Este de fapt echivalentul buclelor cu numar fix de repetitii
din limbajele secventiale.
Exemplu:
INT x:
SEQ i = 0 FOR 5
input ? x
este echivalent cu:
INT x:
SEQ
input ? x
input ? x
input ? x
input ? x
input ? x
sau cu:
INT x, i:
SEQ
i := 0
WHILE i < 5
SEQ
input ? x
i := i + 1
Obs: indexul este de tip INT si nu trebuie declarat.
indexul poate lua orice nume.
indexul nu poate fi modificat.
Exemplu de citire a elementelor unei matrici:
SEQ i=0 FOR SIZE big.array
input ? big.array[i]
echivalent cu
SEQ
input ? big.array[0]
input ? big.array[1]
input ? big.array[(SIZE big.array) - 1]
Occam permite folosirea variabilelor atat pentru baza cat si pentru
contor.
Atentie:
Este interzisa atribuirea sau intrarea intr-un index de
replicator. Nu se poate in nici un fel termina mai repede
un replicator. Ei nu sunt bucle ci matrici de procese
care nu se pot termina decat atunci cand toate procesele
se termina.
PAR replicat
Un PAR replicat construieste o matrice de procese care sunt
structural similare si care se executa in paralel. Orice proces
poate fi identificat su ajutorul indexului replicatorului.
PAR replicat are o importanta deosebita in programarea Occam.
Folosit in conjunctie cu matrici de canale, permite implementarea
eleganta si economica a unor structuri de date cum ar fi buffere sau
cozi, dar mai mult, permite exploatarea concurenta a mai multor
procesoare folsind tehnici cum ar fi benzi de asamblare.
Nota: Anumite implementari nu permit decat constante in campul
count din PAR replicat.(nu variabile).
Exemplu: O coada poate fi implementata in Occam ca o matrice
de canale si o matrice de procese paralele care-si paseaza
o valoare de la unul la celelalt pe canale.
---------> $ $$$$$$$$$$$$$$$$$$$$ --------> $
valoarea coada de lungime 20 valoarea
de intrare de iesire
O aplicatie tipica de folosire a unei cozi este pentru bufferarea
tastaturii. Unele programe accepta caractere mai incet decat poate
tipari un utilizator experimentat. Daca folosim o coada care sa
stocheze aceste caractere, atunci utilizatorul poate tipari cat de
repede doreste, coada crescind de fiecare data cand ei tiparesc
mai repede decat programul poate accepta.
In Occam o coada poate fi simulata printr-o matrice de procese
paralele care-si paseaza datele unul altuia pe un lant de canale.
[21]CHAN OF INT slot:
PAR i=0 FOR 20
WHILE TRUE
INT x:
SEQ
slot[i] ? x
slot[i+1] ! x
slot[0] -> x -> slot[1] ...... slot[19] -> x -> slot[20]
| | | |
+---proces 0 ---+ +---proces 19 ---+
Acesta nu este un program complet pentru ca primul proces are
nevoie de un partener de unde sa citeasca, iar ultimul are
nevoie de un partener unde sa trimita. Altfel fiecare proces
va astepta si intreaga coada se blocheaza. Presupunem ca aceasta
coada face parte dintr-un program mai mare care alimenteaza
coada pe slot[0] si goleste coada pe slot[20].
PAR
...producator
...coada
...consumator
Daca alimentarea cozii se intrerupe temporar, datele care sunt deja
in coada vor continua se se propage, iar procesele de la intrarea
cozii se vor bloca in asteptarea de noi date.
Cand alimentarea cozii reporneste, procesele de la intrare pot
incepe, dar cele de la iesire vor fi blocate in asteptare. In acest
fel coada este constituita din variabilele x locale proceselor
paralele, iar sincronizarea la coada plina si coada goala se face
automat prin comunicatia sincrona a canalelor Occam.
Sa consideram un program care implementeaza un buffer de tastatura.
Tastele sunt puse intr-o coada de 40 de taste, iar de acolo sunt
trimise la ecran.
In ansamblu, programul are 3 procese paralele:
PAR
...producator
...coada
...consumator
[41]CHAN OF INT buff:
PROC keyboard.input ()
WHILE TRUE
INT x:
SEQ
keyboard ? x
buff[0] ! x
:
PROC coada ()
PAR i=0 FOR 40
WHILE TRUE
INT x:
SEQ
buff[i] ? x
buff[i+1] ! x
:
PROC terminal.output ()
WHILE TRUE
INT x:
SEQ
buff[40] ? x
terminal ! x
:
PAR -- programul principal
keyboard.input ()
coada ()
terminal.output ()
Se presupune ca keyboard si terminal sunt canale care deja
exista in sistem.
Un program de sortare paralela pipeline
Consideram un vector de n elemente si o matrice de n procese
paralele , cate un proces pentru fiecare element,
conectate intre ele in cascada prin canale. Elementele trec
prin salba de procese, si fiecare proces intarzie elementele
mai mari. Astfel dupa primul proces, cel mai mare element va
fi ultimul, dupa al doilea, cele mai mari 2 elemente vor fi
ultimele in ordine si asa mai departe.
Fiecare proces are 2 variabile locale highest si next.
Elementele sunt citite in next si comparate cu highest.
Daca next este mai mic este trimis mai departe, daca nu
highest este trimis mai departe si next este memorat in
highest.
primul proces
nesortat highest next
5 2 8 4 7 -> 7 4 -------------> 4
7 8 ----------> 7
8 2 -------> 2
8 5 ----> 5
8 -> 8
al II-lea proces
8 5 2 7 4 -> 4 7 -------------> 4
7 2 ----------> 2
7 5 --------> 5
7 8 ----> 7
8 -> 8
Banda de asmblare arata astfel:
5 2 8 4 7 -> p0 -> p1 -> p2 -> p3 -> p4 -> 8 7 5 4 2
c0 c1 c2 c3 c4 c5
unde p este un proces, iar c este un canal.
Vom scrie un program pentru 100 de numere.
Mai intai trebuie sa definim o matrice de canale pentru pipeline.
[101]CHAN OF INT pipe:
La baza programului sta procesul care compara valorile:
SEQ
pipe[i] ? next -- citeste noua valoare
IF
next <= highest -- daca nu este mai mare
pipe[i+1] ! next -- o trimitem mai departe
next > highest -- daca este mai mare
SEQ
pipe[i+1] ! highest -- trimitem valoarea mai mica
highest := next -- memoram valoarea mai mare
Acest lucru trebuie facut de 99 de ori, o data pentru fiecare
element care trece prin procesul i.
INT highest:
SEQ
pipe[i] ? highest -- initializam highest
SEQ j=0 FOR 99
INT next:
SEQ
pipe[i] ? next
IF
next <= highest
pipe[i+1] ! next
next > highest
SEQ
pipe[i+1] ! highest
highest := next
pipe[i+1] ! highest -- trimitem totusi si pe highest
Sunt 100 de procese paralele care fac acelasi lucru:
PAR i=0 FOR 100
input IS pipe[i]
output IS pipe[i+1]
INT highest:
SEQ
input ? highest -- initializam highest
SEQ j=0 FOR 99
INT next:
SEQ
input ? next
IF
next <= highest
output ! next
next > highest
SEQ
output ! highest
highest := next
output ! highest -- trimitem totusi si pe highest
Mai sunt 2 procese care comunica cu exteriorul, fara a face
nici o prelucrare.
SEQ i=0 FOR 100
INT numar.nesortat:
SEQ
input ? numar.nesortat
pipe[0] ! numar.nesortat
SEQ i=0 FOR 100
INT numar.sortat
SEQ
pipe[100] ? numar.sortat
output ! numar.sortat
Intregul program arata astfel:
VAL numbers IS 100:
[numbers+1]CHAN OF INT pipe:
PAR
PAR i=0 FOR numbers
input IS pipe[i]
output IS pipe[i+1]
INT highest:
SEQ
input ? highest -- initializam highest
SEQ j=0 FOR numbers - 1
INT next:
SEQ
input ? next
IF
next <= highest
output ! next
next > highest
SEQ
output ! highest
highest := next
output ! highest -- trimitem totusi si pe highest
SEQ i=0 FOR 100
INT numar.nesortat:
SEQ
input ? numar.nesortat
pipe[0] ! numar.nesortat
SEQ i=0 FOR 100
INT numar.sortat
SEQ
pipe[100] ? numar.sortat
output ! numar.sortat
Putem optimiza programul tinand cont ca la procesul i din
pipeline, ultimele i elemente sunt sortate si nu mai are
sens sa le comparam.
PAR i=0 FOR numbers
input IS pipe[i]
output IS pipe[i+1]
SEQ
VAL inca.nesortat IS numbers - i :
INT highest:
SEQ
input ? highest -- initializam highest
SEQ j=0 FOR inca.nesortat - 1
INT next:
SEQ
input ? next
IF
next <= highest
output ! next
next > highest
SEQ
output ! highest
highest := next
output ! highest -- trimitem totusi si pe highest
VAL deja.sortat IS i:
SEQ j=0 FOR deja.sortat
INT trece:
SEQ
input ? trece
output ! trece
Terminarea
In acest exemplu, numarul valorilor ce trebuie sortate este
cunoscut in avans asa ca procesele de alimentare si consumare
au putut sa fie scrise cu ajutorul unui SEQ replicat care se
termina singur.
In programele unde numarul de valori care traverseaza o banda
de asamblare nu este cunoscut, procesele producator si consumator
trebuie sa foloseasca bucle WHILE si apare problema terminarii
elegante a benzii de asamblare.
Pentru benzile de asamblare cea mai buna schema este ca procesul
care alimenteaza banda sa trimita un mesaj de terminare care sa
se propage prin toata banda pana la consumator, fiecare proces
din banda oprindu-se dupa ce a propagat acest mesaj. Altfel, daca
ne bazam pe terminarea streamului de intrare, banda se poate bloca
cu date valide inca nescoase la capat.
Occam interzice folosirea solutiei cu un flag global care sa-si
schimbe valoarea din "start" in "stop", deoarece aceast lucru
violeaza regule cu privire la modificarea variabilelor partajate
intr-un PAR.
ALT replicat
Consta dintr-un numar de alternative structural identice, fiecare
fiind declansata de intrarea de pe un canal. Ca si PAR replicat,
este un constructor foarte puternic in Occam.
Exemplu:
[80]CHAN OF INT incoming:
CHAN OF INT outgoing:
PAR
...procese ce trimit pe canalele incoming
WHILE TRUE
INT x:
ALT i=0 FOR 80
incoming[i] ? x
outgoing ! x
In acest exemplu, ALT replicat este echivalent cu un ALT cu 80
de alternative:
ALT
incoming[0] ? x
outgoing ! x
incoming[1] ? x
outgoing ! x
...
deci programul se comporta ca un multiplexor pe canalul outgoing.
IF replicat
Produce o constructie conditionala cu un numar de variante
structurate similar.
Exemplu:
IF i=0 FOR 5
array[i] = 0
array[i] := 1
verifica primele 5 elemente din array si inlocuieste primul 0
cu un 1. Daca nici un element nu este 0, atunci se blocheaza.
Solutie:
IF
IF i=0 FOR 5
array[i] = 0
array[i] := 1
TRUE
SKIP
Sa rescriem functia care gaseste prima aparitie a unui caracter
intr-un string:
INT FUNCTION find.char (VAL BYTE char, VAL []BYTE string)
VAL INT negasit IS -1:
INT raspuns:
VALOF
IF
IF index=0 FOR SIZE string
string[index] = char
raspuns := index
TRUE
raspuns := negasit
RESULT raspuns
:
9. Programarea in timp real in Occam
Timere
Un timer este un obiect de tipul TIMER. Se comporta ca un canal
care poate fi doar citit, dar permite ca mai multe procese
intr-un PAR sa-l citeasca.
Valoarea citita de la un timer este timpul curent reprezentat
ca un intreg.
TIMER clock:
INT time:
clock ? time
Nota: Ticurile unui timer variaza de la o implementare la alta,
in functie de hardware-ul pe care ruleaza. Pe transputerul
INMOS, ticurile sunt in unitati (periada ceasului) / (5*64)
ceea ce in mod normal inseamna 64 microsecunde pe tic.
Timpul se masoara din momentul in care se porneste sistemul,
sau sistemul are o baterie pentru un ceas de timp real.
Cand valoarea timpului depaseste valoarea maxima
reprezentabila intr-un INT, devine negativa si continua
incrementarea spre 0. Daca un tic are durata de 64 usec
si INT este reprezentat pe 16 biti acest lucru se
intampla la 4.2 sec, iar daca INT este pe 32 biti la 76 ore.
Diferentele de timp trebuie deci calculate cu operatori
modulo, iar intervalele lungi trebuie masurate prin spargerea
lor in intervale mai mici. Notati ca o secunda are exact
15 625 ticuri de 64 usec.
Se recomanda declararea mai multor timere intr-un program, chiar daca
valorile returnate de toate sunt aceleasi (atunci cand programul se
ruleaza pe un singur procesor). Daca avem mai multe programe paralele
independente care toate folosesc timere, independenta lor trebuie
exprimata prin folosirea de timere diferite.
Intarzieri
Se pot implementa cu ajutorul timerelor. Se provoaca o blocare
pe citirea unui timer pina cand valoarea timerului este egala
cu valoarea furnizata cu ajutorul operatorului AFTER.
AFTER urmat de o expresie reprezentind un timp este folosit
pentru a provoca o intarziere.
PROC delay (VAL INT interval)
TIMER clock:
INT timpcurent:
SEQ
clock ? timpcurent
clock ? AFTER timpcurent PLUS interval
delay (6000) va provoca o intarziere de 6000 de ticuri.
O intarziere poate fi folosita intr-un ALT pentru a implementa
o asteptare in timp-real:
TIMER clock:
VAL timeout IS 1000:
INT timpcurent:
SEQ
clock ? timpcurent
INT x:
ALT
input ? x
...proces
clock ? AFTER timpcurent PLUS timeout
warning ! (17::"Timeout on input!")
AFTER poate fi folosit si ca operator de comparatie care
intoarce o valoare booleeana. x AFTER y este echivalent cu
(x MINUS y) > 0.
Pentru operatiile cu timpi trebuie folosita mereu aritmetica
modulo, pentru ca timerele asa functioneaza.
AFTER poate fi folosit in conditii pentru a testa care timp
este inaintea altuia:
TIMER clock:
INT proc1.time, proc2.time:
SEQ
PAR
SEQ
...proces 1
clock ? proc1.time
SEQ
...proces 2
clock ? proc2.time
IF
proc1.time AFTER proc2.time
...restul programului
Obs: testul are sens doar daca diferenta de timp este mica in
comparatie cu cea mai mare valoare reprezentata intr-un
intreg.
Prioritati
Ce se intimpla daca intr-un ALT daca mai multe canale devin
simultan disponibile? Nu se stie care dintre ele va fi aleasa.
In programarea in timp real este necesar sa stim ce se intampla
in astfel de cazuri. Occam permite ALT si PAR cu prioritati.
ALT prioritar (PRI ALT)
Cand 2 intrari devin disponibile simultan este considerata cea
mai prioritara, adica aceea scrisa textual mai intai.
Un exemplu special de utilizare a lui PRI ALT este rutina de
garantare a observarii unui canal ce transmite un semnal
important:
WHILE cycling
PRI ALT
quit ? any
cycling := FALSE
TRUE & SKIP
...ciclul principal
TRUE & SKIP este mereu disponibil, iar daca nu foloseam PRI ALT
ci doar ALT, riscam sa executam mereu ramura TRUE & SKIP, desi
aveam semnal de stop pe canalul quit.
PAR prioritar (PRI PAR)
In PRI PAR, procesele cu prioritate mai mica (cele scrise la
urma) nu vor fi executate decat daca procesele mai prioritare
(cele scrise inintea lor) nu se pot executa datorita asteptarii
de evenimente.
PRI PAR
SEQ
input1 ? x
output1 ! x
SEQ
input2 ? y
output2 ! y
Al 2-lea SEQ nu se executa, chiar daca input2 este disponibil,
decat daca primul SEQ asteapta la unul din canalele input1 sau
output1.
PRI PAR este folosit in anumite aplicatii de timp real pentru
a servi un dispozitiv destul de repede atunci cand calculatorul
are si alte activitati de facut.
VAL blocksize IS 1024:
CHAN OF [blocksize]BYTE nextblock:
PRI PAR
WHILE TRUE
[blocksize]BYTE block:
SEQ
nextblock ? block
SEQ i=0 FOR blocksize
squirtout ! block[i]
...procesul principal
In acest program, procesul principal este executat doar in
momentele cand dispozitivul conectat la squirtout nu este
servit.
Bufferare
Nu are sens sa rulezi un proces la mare prioritate pentru a
servi un dispozitiv nerabdator, daca acest proces se poate
bloca in comunicatia cu alte procese.
Un astfel de proces trebuie sa aiba toate comunicatiile cu
alte procese bufferate.
VAL blocksize IS 1024:
CHAN OF [blocksize]BYTE nextblock:
PRI PAR
CHAN OF [blocksize]BYTE bloc.bufferat
PAR
WHILE TRUE -- procesul de bufferare
[blocksize]BYTE block:
SEQ
nextblock ? block
bloc.bufferat ! block
WHILE TRUE -- procesul de servire
[blocksize]BYTE block:
SEQ
bloc.bufferat ? block
SEQ i=0 FOR blocksize
squirtout ! block[i]
...procesul principal
In acest exemplu intrarea de pe canalul nextblock are un buffer
suplimentar pentru inca un bloc. Acesta reduce sansele ca rutina de
servire sa astepte intrarea unui nou bloc ceea ce ar provoca
intarzierea tratarii dispozitivului.
Obs: Incercati sa nu folositi PRI PAR decat daca este absolut
necesar.
Prioritati si configurare
Prioritatile sunt o problema de configurare pentru ca nu afecteaza
logica programelor. Problemele de configurare sunt acelea care
urmaresc criterii de performanta.
Programele trebuie scrise folosind PAR si ALT normale, iar
prioritatile, daca este nevoie de ele, trebuie nefolosite pana
cand logica programului functioneaza corect.
10. Configurare
Idea: Configurarea nu afecteaza comportamentul logic al unui
program. Permite aranjarea programului pentru o mai buna
performanta.
Canale hard
In Occam comunicatia cu lumea exterioara (input/output) se face
prin canale conectate la dispozitive hardware din lumea "reala".
In orice implementare particulara de Occam exista un numar de
canale la dispozitia programelor. Aceste canale pot in unele
cazuri sa conduca direct la hardware via drivere legate la
program, sau in alte cazuri pot conduce la sistemul de operare
al calculatorului gazda care are grija de I/O.
Un sistem tipic Occam suporta cel putin canale pentru ecran,
tastatura si sistemul de fisiere. Acetor canale le sunt asignate
numere. Sa presupunem ca avem urmatoarea numerotare pentru
canalele hard:
1 Output la ecran
2 Input de la tastatura
Acestor numere le putem asocia nume pe care le vom folosi drept
canale in program:
PLACE screen AT 1 :
PLACE keyboard AT 2 :
In program aceste canale pot fi folosite ca orice canal:
VAL mesaj IS "Hello world!" :
SEQ i=0 FOR SIZE mesaj
screen ! INT mesaj[i]
sau
keyboard ? x
Nota: Intregii trimisi sunt de obicei coduri ASCII.
Protocoale hardware
Unele dispozitive hard necesita protocoale variate, adica
trebuie trimise comenzi si caractere speciale pe langa
caracterele de date.
De exemplu, un canal pentru un display poate fi bufferat
si astfel caracterele sunt stranse intr-un buffer si
trimise la ecran doar la cerere speciala de golire buffer
(un caracter de sfarsit, sa spunem -2), sau cand s-au strans
80 de caractere (o linie).
Deci va trebui sa scriem:
VAL endrecord IS -2 :
...
SEQ
VAL mesaj IS "Hello world!" :
SEQ i=0 FOR SIZE mesaj
scren ! INT mesaj[i]
screen ! endrecord
pentru a fi siguri ca mesajul este tiparit imediat. Altfel,
ar putea fi tiparit dupa ce mai sunt trimise spre ecran inca 68
de caractere.
In exemplele folosite pana acum am folosit canalele input si
output. Daca aceste sunt declarate:
PLACE input AT 2 :
PLACE output AT 1 :
programele ar putea fi testate la un terminal cu tastatura si
display.
Protocolul anarhic
In cazurile cand un protocol de canal este dependent de natura
dispozitivului cu care comunica (imprimanta, controlor de disc),
Occam ofera un protocol special, protocolul anarhic ANY :
CHAN OF ANY screen:
Practic aceasta declaratie specifica un canal care poate
transporta obiecte singulare de orice tip.
SEQ
screen ! 1024
screen ! 'c'
sunt ambele iesiri valide pe canalul screen. Iesirea valorii INT
1024 este sparta in octetii componenti (2 sau 4) si trimisa octet
cu octet. Deci valorile sunt trimise ca streamuri de octeti.
Porturi
Pe langa canale hard, Occam poate adresa porturile I/O asa
cum sunt ele folosite in sistemele conventionale.
O declaratie de port are un tip de date:
PORT OF BYTE serial1:
Porturile sunt folosite precum canalele, dar comunicatia nu
mai este sincronizata. Conteaza momentul cand se face citirea
sau scrierea unui port.
Exemplu:
utilizarea unui UART (Universal Asynchronous Receiver/Transmiter)
PORT OF BYTE status.reg, data.reg:
PLACE status.reg AT #3801 :
PLACE data.reg AT #3803 :
VAL rx.ready IS #01 -- first bit mask
VAL tx.ready ID #04 -- third bit mask
PROC delay () -- wait aprox 1 msec
VAL interval IS 1000/64:
INT timenow:
SEQ
clock ? timenow
clock ? AFTER timenow PLUS interval
:
PROC poll (INT ready) -- wait for status
BYTE status:
SEQ
status.reg ? status
WHILE ((INT status) /\ ready) = 0
SEQ
delay ()
status.reg ? status
:
INT data:
PROC read.data () -- read a character
BYTE char:
SEQ
poll (rx.ready) -- poll on first bit
data.reg ? char
data := (INT char) /\ #7F
:
PROC write.data () -- write a character
SEQ
poll (tx.ready) -- poll on third bit
data.reg ! BYTE (data /\ #7F)
:
Alocare
Programele Occam pot fi concepute, scrise, testate si depanate
pe o statie de lucru cu un singur procesor si apoi transferate
pe o retea de calculatoare paralele.
Ultimul stagiu al acestui ciclu este alocarea de procese
paralele din program la diferite procesoare. Aceasta alocare
este realizata prin inlocuirea PAR cu PLACED PAR in locurile
potrivite din program. PLACED PAR este urmat de o plasare
care consta din numarul procesorului si procesul ce trebuie
rulat pe el.
Ca exemplu, sa cosideram sortarea paralela pipeleine alocata
pe o matrrice de 101 transputere:
PLACED PAR
PLACED PAR i=0 FOR numbers
PROCESSOR i
input IS pipe[i]
output IS pipe[i+1]
INT highest:
SEQ
input ? highest -- initializam highest
SEQ j=0 FOR numbers - 1
INT next:
SEQ
input ? next
IF
next <= highest
output ! next
next > highest
SEQ
output ! highest
highest := next
output ! highest -- trimitem totusi si pe highest
...restul sortarii
Cuvintele cheie PLACE ... AT ... aloca o variabila, un canal,
un port sau o matrice unei adrese fizice de memorie.
Un exemplu tipic ar fi scrierea directa in memoria video:
VAL screenstart IS #B800:
[32000]BYTE screenbuffer:
PLACE screenbuffer AT screenstart:
...
screenbuffer[57] := #FF
11. Terminarea programelor concurente
Asigurarea terminarii corecte a programelor este o problema
inerenta in programarea concurenta.
In programarea secventiala exista un singur flux de control,
o singura cale de rulare a programului.
In programarea concurenta, exista mai multe procese paralele.
Daca procesele nu comunica intre ele, oprirea unuia le va lasa
pe celelalte mergind. Daca procesele comunica, oprirea unui
proces poate bloca celelalte procese mai degraba decat sa
le opreasca.
Obs: Nu trebuie folosit STOP pentru a opri un proces. Asa cum este
definit STOP nu se opreste niciodata.
Variabile partajate
O metoda evidenta de incercat, bazata pe experianta programarii
secventiale este folosirea unei variabile globale pe post de flag
care este setat pe "stop" sau "start" si este citit de toate
procesele. Acest lucru nu este permis in Occam daca procesele sunt
intr-un PAR. Proceselor paralele nu le este permis sa partajeze
o variabila care este modificata prin atribuire sau intrare de pe
un canal.
Daca s-ar permite, nu ar fi satisfacator. Cand variabila este
setata pe "stop", s-ar putea ca nu toate procesele sa poata
termina. sistemul ar fi lasat intr-o stare confuza, cu operatiile
partial terminate si cu datele in locuri nepotrivite.
S-ar putea sa permitem fiecarui proces sa termine dupa ce a
primit mesajul "stop". Dar ce se intampla daca are nevoie de
serviciul unui alt proces care deja s-a terminat?
Nu exista solutie in aceasta privinta. Sistemul trebuie privit
in ansamblu iar oprirea lui trebuie propagata prin el intr-o
maniera deliberata.
Terminarea lui PAR
Sa luam ca exemplu o banda de asamblare implementata cu un
PAR replicat.
-> intrare -> pipeline (n) -> iesire ->
Trebuie sa lamurim cum se termina sirul de intrare. Nu putem testa
lipsa de date pentru ca asta inseamna blocare. Trebuie sa
definim un obiect de sfarsit care ne indica faptul ca nu mai
sunt date. Sa consideram ca trimiterea unui numar negativ
inseamna sfarsitul sirului de numere.
Acum, intrarea se poate termina:
PROC intrare ()
INT x:
BOOL more:
SEQ
more := TRUE
WHILE more
SEQ
input ? x
IF
x < 0
more := FALSE
x >= 0
pipe[0] ! x
Daca procedam asa se termina intrarea dar celelalte procese
din pipeline si iesirea raman in acelasi impas de mai inainte.
Trebuie procedat in felul urmator:
1. intrarea detecteaza sfarsit, il trimite mai departe
si apoi se opreste
2. toate procesele din pipeline fac la fel cu intrarea.
3. iesirea pur si simplu se termina cand primeste indicatorul
de sfarsit.
In Occam acestea devin:
PROC intrare ()
INT next:
BOOL more:
SEQ
more := TRUE
WHILE more
SEQ
input ? next
IF
next < 0
SEQ
pipe[0] ! next
more := FALSE
next >= 0
pipe[0] ! next
:
PROC iesire ()
INT next:
BOOL more:
SEQ
more := TRUE
WHILE more
SEQ
pipe[n] ? next
IF
next < 0
more := FALSE
next >= 0
output ! next
:
PROC pipeline ()
PAR i=0 FOR n
input IS pipe[i]
output IS pipe[i+1]
INT next:
BOOL more:
SEQ
more := TRUE
WHILE more
SEQ
input ? next
IF
next < 0
SEQ
output ! next
more := FALSE
next >= 0
SEQ
...some processing
output ! next
:
PAR
intrare ()
pipeline ()
iesire ()
Este greu sa dam o metoda generala de terminare a unui PAR, detaliile
variaza de la caz la caz. Dar putem da cateva principii generale
care pot fi utile:
1. Terminarea corecta implica cunoastere. Procesele trebuie sa
stie cand sa se termine.
2. Cunoasterea implica raspandirea ei. Nu exista un telegraf
magic care sa anunte toate procesele dintr-un PAR simulatan.
3. Canalele sunt vehiculele cele mai potrivite pentru raspandirea
cunoasterii intr-un PAR.
4. Nu exista nici un loc in spatiul unui computer in afara unui
PAR. Controlul poate veni doar de la o alta componenta a
PAR-ului printr-un canal.
Terminarea lui ALT
Terminarea lui ALT este destul de simpla. Trebuie monitorizat
un canal ce poate aduce mesajul de stop.
VAL quit IS FALSE:
CHAN OF BOOL signal:
...
BOOL going:
SEQ
going := TRUE
WHILE going
INT var1, var2:
ALT
chan1 ? var1
...proces1
chan2 ? var2
...proces2
signal ? going
SKIP
Problema este ca daca in momentul sosirii mesajului quit, unul
din celelalte canale este permanent disponibil, este posibil
sa nu fie luat in considerare mesajul de quit.
Aceasta situatie se rezolva cu un ALT prioritar, facand canalul
de stop prioritar.
...
PRI ALT
signal ? quit
SKIP
chan1 ? var1
...proces1
chan2 ? var2
...proces2
12. Stilul de programare Occam
Sa consideram un program care numara cuvintele tiparite si
afiseaza acest numar din minut in minut.
-- external channels
CHAN OF INT Keyboard:
PLACE Keyboard AT 2:
CHAN OF INT Screen:
PLACE Screen AT 1:
-- screen output
VAL Return IS '*c' (INT):
VAL Space IS '*s' (INT):
VAL Tab IS '*t' (INT):
VAL Newline IS '*n' (INT):
VAL ControlC IS 3:
VAL Bell IS 7:
VAL EndRecord IS -2:
PROC OutputChar (VAL INT Char)
Screen ! Char
:
PROC FlushScreen ()
OutputChar (EndRecord)
:
PROC OutputString (VAL []BYTE Mesaj)
SEQ i=0 FOR SIZE Mesaj
Screen ! INT Mesaj[i]
:
PROC OutputNumber (VAL INT Number)
VAL Sute IS Number / 100:
VAL Rest IS Number REM 100:
VAL Zeci IS Rest / 10:
VAL Unitati IS Rest REM 10:
VAL MakeDigit IS '0' (INT):
IF
Number < 10
SEQ
OutputChar (Unitati + MakeDigit)
FlushScreen ()
Number < 100
SEQ
OutputChar (Zeci + MakeDigit)
OutputChar (Unitai + MakeDigit)
FlushScreen ()
Number < 1000
SEQ
OutputChar (Sute + MakeDigit)
OutputChar (Zeci + MakeDigit)
OutputChar (Unitati + MakeDigit)
FlushScreen ()
:
-- time accumulation
TIMER Clock:
VAL TicksPerSecond IS 15625:
VAL OneMinute IS 60 * TicksPerSecond:
INT NextMinute, TotalMinutes:
PROC InitTimer ()
INT TimeNow:
SEQ
Clock ? TimeNow
NextMinute := TimeNow PLUS OneMinute
TotalMinutes := 0
:
PROC UpdateTimer ()
SEQ
NextMinute := NextMinute PLUS OneMinute
TotalMinutes := TotalMinutes + 1
:
-- word accumulation
INT WordsThisMinute, TotalWords:
PROC InitWordCounts ()
SEQ
WordsThisMinute := 0
TotalWords := 0
:
PROC UpdateWordCounts ()
SEQ
TotalWords := TotalWords + WordsThisMinute
WordsThisMinute := 0
:
-- statistics
PROC ShowCurrentSpeed ()
VAL VeryFewWordsPerMinute IS 5:
IF
WordsThisMinute < VeryFewWordsPerMinute
SEQ
OutputChar (Bell)
OutputString (" [Still awake?] ")
FlushScreen ()
WordsThisMinute >= VeryFewWordsPerMinute
SEQ
OutputString ( " [")
OutputNumber (WordsThisMinute)
OutputString (" words/min]")
FlushScreen ()
:
PROC ShowAverageSpeed ()
VAL AverageSpeed IS (TotalWords / TotalMinutes) :
SEQ
OutputChar (Newline)
OutputString(" You typed ")
OutputNumber(TotalWords)
OutputString(" words in ")
OutputNumber(TotalMinutes)
OutputString(" minutes.")
OutputChar (Newline)
OutputString(" Average speed = ")
OutputNumber(AverageSpeed)
OutputString(" words per minute.")
FlushScreen()
:
BOOL InWord, Active: -- main process
SEQ
InitTimer()
InitWordCounts()
Active := TRUE
InWord := FALSE
WHILE Active
INT Char:
ALT
NOT InWord & Clock ? AFTER NextMinute
SEQ
ShowCurrentSpeed()
UpdateWordCounts()
UpdateTimer()
Keyboard ? Char
IF
Char = ControlC
Active := FALSE
(Char = Space) OR (Char = Return) OR (Char = Tab)
SEQ
InWord := FALSE
IF
Char = Return
OutputChar(Newline)
Char <> Return
OutputChar(Char)
FlushScreen()
NOT InWord
SEQ
InWord := TRUE
WordsThisMinute := WordsThisMinute + 1
OutputChar(Char)
FlushScreen()
InWord
SEQ
OutputChar(Char)
FlushScreen()
ShowAverageSpeed()
Acest program puncteaza urmatoarele despre stilul de programare
Occam:
1. Utilizarea procedurilor. Gruparea a cat mai mult cod in
proceduri decat in bucla principala. Factorizarea cat mai mare.
2. Declaratiile si abrevierile sunt grupate cu procedurile
care le uttilizeaza. Este mult mai usor sa gasesti lucruri
daca iti alegi o ordine in care sa faci declaratiile si
abrevierile. In cest program s-a folosot ordinea:
canale > timere > abrevieri > variabile
3. Folosirea libera a abrevierilor. Nu costa nimic si pot mari
performantele permitand compilatorului sa-si aleaga mai
eficient implementarile. De exemplu:
[1010]INT fred:
SEQ i=0 FOR 1000
...
WHILE foo > 0
VAL f IS [fred FROM i FOR 10]:
SEQ
...
utilizarea lui f va mari viteza de executie deoarece f devine
o variabila locala buclei interne si deoarece f este recunoscuta
ca o constanta asa ca verificarile la rulare sunt reduse.
Constantele cu nume fac programele mult mai citibile.
4. Alegeti-va un stil de botezare a variabilelor si numelor.
In acest program numele incel cu litere mari si continua
cu litere mici. Numele compuse sunt separate de majuscule.
5. Pastrati specificatiile de toate felurile locale. In mod
normal trebuie plasate imediat inainte de procesul in care
sunt folosite.
6. Occam se preteaza foarte bine la factorizarea in proceduri
cu caracter de filtru.
Canalele sunt solutii naturale de conectare a filtrelor.
7. Nu fiti preocupati de configurare si prioritati decat in faza
finala a programului. Intelegeti logica programului inainte
sa considerati adevaratul hardware pe care se va rula
programul. Scrieti programul far plasari si prioritati si
abia dupa ce merge cofigurati-l pentru un hardware specific.
8. Invatati-va singuri sa decoperiti paralelismul dintr-o
problema. Este foarte simplu sa facem un program secvential
din unul paralel. Invers insa putem fi nevoiti sa rescriem
complet programul.
9. In Occam se poate descrie foarte natural o problema.
Intimplator descrierea facuta este chiar un program.

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