You are on page 1of 169

I.

Lovrek, G. Ježić, S. Dešić, M. Kušek







Diplomski studij



Informacijska i
komunikacijska
tehnologija

Telekomunikacije i

informatika






Konkurentno
programiranje

Radna inačica udžbenika










Ak. g. 2016./2017.
Zaštićeno licencom http://creativecommons.org/licenses/by-nc-sa/2.5/hr/




Slobodno smijete:
• dijeliti — umnožavati, distribuirati i javnosti priopćavati djelo
• remiksirati — prerađivati djelo

pod sljedećim uvjetima:
• imenovanje. Morate priznati i označiti autorstvo djela na način kako je specificirao
autor ili davatelj licence (ali ne način koji bi sugerirao da Vi ili Vaše korištenje njegova
djela imate njegovu izravnu podršku).
• nekomercijalno. Ovo djelo ne smijete koristiti u komercijalne svrhe.
• dijeli pod istim uvjetima. Ako ovo djelo izmijenite, preoblikujete ili stvarate koristeći
ga, preradu možete distribuirati samo pod licencom koja je ista ili slična ovoj.

U slučaju daljnjeg korištenja ili distribuiranja morate drugima jasno dati do znanja licencne
uvjete ovog djela. Najbolji način da to učinite je linkom na ovu internetsku stranicu.
Od svakog od gornjih uvjeta moguće je odstupiti, ako dobijete dopuštenje nositelja
autorskog prava.
Ništa u ovoj licenci ne narušava ili ograničava autorova moralna prava.
Tekst licencije preuzet je s http://creativecommons.org/.

Sadržaj

1. UVOD.................................................................................................................................... 3
2. PROGRAMSKI JEZICI – KONKURENTNOST I PARALELIZAM ..................................... 4
1.1 Povijesni razvoj programskih jezika....................................................................... 4
1.2 Vrste jezika ................................................................................................................. 6
1.3 Značajke jezika s motrišta primjene....................................................................... 8
1.4 Konkurentnost i paralelizam u programskim jezicima ........................................ 9
3. PROCESORI I UPRAVLJAČKI SUSTAVI......................................................................... 10
1.5 Razine upravljanja ................................................................................................. 10
1.6 Modeli procesorskih sustava ................................................................................. 11
4. PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA .................. 17
1.7 Predočavanje paralelizma u programskim jezicima ....................................... 17
1.7.1 Korutine ........................................................................................................... 17
1.7.2 Paralelne naredbe ........................................................................................ 17
1.7.3 Proces .............................................................................................................. 18
1.7.4 Nit ..................................................................................................................... 19
1.7.5 Komunikacija i sinkronizacija ....................................................................... 19
1.7.6 Zajednička varijabla .................................................................................... 20
1.7.7 Izmjena poruka .............................................................................................. 20
1.8 Međuprocesna komunikacija izmjenom poruka ................................................ 21
1.8.1 Imenovanje procesa ...................................................................................... 21
1.8.2 Suradnja procesa .......................................................................................... 23
1.8.3 Način izmjene poruka ................................................................................... 23
1.8.4 Način prijama poruke................................................................................... 25
1.8.5 Smjer komunikacije ........................................................................................ 26
1.8.6 Stvarnovremeni aspekti ................................................................................ 28
5. DEKLARATIVNI KONKURENTNI JEZIK ERLANG ......................................................... 30
1.9 Osnovne karakteristike .......................................................................................... 30
1.10 Osnovni funkcijski koncepti.................................................................................... 31
1.11 Aritmetički izrazi ..................................................................................................... 34
1.12 Slijedno programiranje ......................................................................................... 34
1.12.1 Vrste podataka u Erlangu ........................................................................... 34
1.12.2 Varijable ......................................................................................................... 36
1.12.3 Podudaranje uzoraka................................................................................... 37
1.12.4 Pozivanje funkcija .......................................................................................... 38
1.12.5 Programi u Erlangu ....................................................................................... 38
1.12.6 Klauzule ........................................................................................................... 39
1.12.7 Uvjeti za izvršenje klauzula ......................................................................... 40
1.12.8 Primjeri primjena Erlang funkcija i konstrukcija ....................................... 40
1.13 Konkurentno programiranje .................................................................................. 42
1.13.1 Osnovni pojmovi ............................................................................................ 42
1.13.2 Kreiranje novog procesa.............................................................................. 43
1.13.3 Razlike između selektivnog i neselektivnog prijema poruka ................ 44
1

1.13.4 Model klijent/poslužitelj ............................................................................... 46


1.13.5 Upotreba vremenske kontrole .................................................................... 47
1.13.6 Upravljanje sklopovskim jedinicama .......................................................... 50
1.14 Raspodijeljeno programiranje i robustnost ........................................................ 50
1.14.1 Referencijska transparentnost ..................................................................... 51
1.14.2 Ugrađene funkcije koje omogućuju raspodijeljeno programiranje...... 51
1.14.3 Robustnost ....................................................................................................... 54
1.14.4 Gašenje procesa ........................................................................................... 58
1.14.5 Povezani procesi ............................................................................................ 58
1.15 Korištenje Erlangove ljuske ................................................................................... 62
1.15.1 Erlang iz naredbenog retka ........................................................................ 63
1.15.2 Korisne naredbe Erlangove ljuske .............................................................. 64
1.16 Korištenje Erlanga iz Eclipsa ................................................................................ 65
6. Konkurentnost u objektno orijentiranim jezicima ........................................................ 67
1.17 Virtualni procesori .................................................................................................. 68
1.18 Kompatibilne aktivnosti ......................................................................................... 68
1.19 Kompleksna konkurentnost .................................................................................... 69
7. Konkurentnost u Javi ........................................................................................................ 70
1.20 Stvaranje i pokretanje niti .................................................................................... 70
1.21 Imenovanje i privremeno zaustavljanje niti........................................................ 71
1.22 Stanja niti ................................................................................................................. 72
1.22.1 Stanje New Thread ........................................................................................ 73
1.22.2 Stanje Runnable .............................................................................................. 73
1.22.3 Stanje Not Runnable ...................................................................................... 73
1.22.4 Stanje Dead .................................................................................................... 74
1.22.5 Iznimka IllegelThreadStateException ................................. 75
1.22.6 Metoda isAlive ....................................................................................... 75
1.22.7 Metoda join................................................................................................ 75
1.23 Deamon niti .............................................................................................................. 76
1.24 Grupa niti ................................................................................................................. 76
1.24.1 Pridruživanje osnovnoj grupi ....................................................................... 76
1.24.2 Klasa ThreadGroup ................................................................................. 77
1.25 Prioriteti niti ............................................................................................................. 77
1.26 Sebične niti ............................................................................................................... 77
1.27 Pravedan sustav...................................................................................................... 79
1.28 Nitna sigurnost......................................................................................................... 79
1.29 Sinkronizacija niti .................................................................................................... 81
1.30 Zaključavanje .......................................................................................................... 82
1.31 Primjer sinkronizacije ............................................................................................. 84
1.32 Problemi kod sinkronizacije .................................................................................. 86
1.33 Koordinacija ............................................................................................................ 86
1.33.1 Metode notifyAll i wait .................................................................... 87
1.34 Sinkronizacijski mehanizmi .................................................................................... 88
1.34.1 Brojeći semafor .............................................................................................. 88
1.34.2 Sinkroniziranje kolekcija podataka ........................................................... 90
2

1.34.3 Sinkronizacija repa ....................................................................................... 92


1.34.4 Izmjenjivač podataka ................................................................................... 95
1.34.5 Prepreka ......................................................................................................... 96
1.34.6 Uzorak zaključavanja ................................................................................... 98
1.35 Recikliranje niti ........................................................................................................ 99
1.36 Primijenjena konkurentnost .................................................................................. 106
1.36.1 Grafika i niti ................................................................................................. 106
1.36.2 Programiranje mrežnog poslužitelja ........................................................ 110
1. POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG....................................... 116
1.1. Povezivanje pomoću procesa ............................................................................. 116
1.1.1. Ports ............................................................................................................... 116
1.2. Povezivanje pomoću mreže ................................................................................ 122
1.3. Povezivanje pomoću biblioteke Jinterface ...................................................... 124
2. PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA PROGRAMA ......... 128
2.1. Definicija modela.................................................................................................. 128
2.2. Donja granica minimalnog broja procesora i minimalnog vremena........... 130
2.3. Postupak procjene minimalnog broja procesora i trajanja programa ...... 132
2.4. Uravnoteženje opterećenja procesora ............................................................. 135
3. RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA
136
3.1. Model raspoređivanja ......................................................................................... 137
3.2. Raspoređivanje poslova genetičkim algoritmom............................................ 139
3.2.1. Opis problema ............................................................................................. 140
3.2.2. Graf poslova ................................................................................................ 141
3.2.3. Međuprocesorska komunikacija ................................................................ 141
3.3. Početna populacija............................................................................................... 142
3.4. Funkcija prikladnosti............................................................................................. 143
3.5. Genetički operatori .............................................................................................. 144
3.5.1. Križanje ......................................................................................................... 144
3.5.2. Reprodukcija ................................................................................................ 147
3.5.3. Mutacija......................................................................................................... 147
3.6. Potpuni algoritam ................................................................................................. 148
4. PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA ......................................... 150
4.1. Jezici ....................................................................................................................... 150
4.1.1. Struktura programskih jezika .................................................................... 151
4.1.2. Složenost programa.................................................................................... 152
4.1.3. Pogreške tijekom izvedbe.......................................................................... 152
4.1.4. Specifičnosti jezičnih paradigmi ............................................................... 153
4.2. OTVORENE PLATFORME ..................................................................................... 159
4.2.1. Generički skup funkcija telekomunikacijskog sustava ........................... 159
4.2.2. Otvoreno procesiranje................................................................................ 161
8. LITERATURA ..................................................................................................................... 166
UVOD 3

1. UVOD
Radna inačica udžbenika "Konkurentno programiranje" namijenjena je studentima
diplomskog studija Informacijska i komunikacijska tehnologija i Računarstvo na
Fakultetu elektrotehnike i računarstva Sveučilišta u Zagrebu. Ova knjiga se bavi
temeljima konkurentnog programiranja , a praktični primjeri su u programskim
jezicima Erlang i Java.
PROGRAMSKI JEZICI – KONKURENTNOST I PARALELIZAM 4

2. PROGRAMSKI JEZICI – KONKURENTNOST I PARALELIZAM

1.1 Povijesni razvoj programskih jezika

Jedan od mogućih pogleda na razvoj jezika je sljedeći:

1954. – 1958. prva generacija

• osnovni koncepti visokih programskih jezika: aritmetički izrazi, naredbe, polje,


lista, složaj, potprogram
• Fortran: i danas u najširoj uporabi za numerička računanja
• Algol: manje važan kao jezik, značajniji zbog utjecaja na razvoj drugih jezika
(tzv. Algolu slični jezici, npr. Pascal, PL/1, Simula, Ada, C)

1959. – 1961. druga generacija

• stabilizacija jezika
• Cobol: jezik s najviše programera i primjena
• Lisp: najvažniji jezik za umjetnu inteligenciju

1962. – 1969. treća generacija

• Pascal: proširenje Algola, prihvaćen u akademskoj sredini, nedovoljno


robustan za industrijske primjene
• PL/1: kombinacija Algol/Fortran/Cobol
• Simula: prvi objektno orijentirani jezik
• Basic: jedan od najraširenijih jezika 80ih godina

1970. – 1979.

• intenzivna istraživanja
• programsko inženjerstvo
• CSP (Communicating Sequential Processes, Hoare): komunicirajući slijedni
procesi, koncepti konkurentnog programiranja
• Ada: modularnost, integracija funkcija s podacima, konkurentnost
• PLEX (Programming Language for EXchanges),
CHILL (CCITT HIgh Level Language): jezici za programiranje komutacijskih
sustava
• C: danas se koristi za programiranje koje je vezano uz hardver
• Smalltalk: jedan od popularnijih objektno orijentiranih jezika
• Scheme: funkcijski jezika temeljen na Lispu
• ML: funkcijski programski jezik opće namjene
• Prolog: logički programski jezik koji se naviše koristi kod umjetne inteligencije

1980. – 1989.

• različite paradigme: objektno orijentirani, raspodijeljeni, konkurentni,


funkcijski, logički jezici
PROGRAMSKI JEZICI – KONKURENTNOST I PARALELIZAM 5

• Occam: konkurentni, imperativni, proceduralni programski jezik korišten kod


transpjutera
• C++: originalno se zvao C s klasama
• Objective-C: objektno orijentirani jezik, danas se prvenstveno koristi za
programiranje na operacijskim sustavima Mac OS X i iPhone OS
• Erlang: konkurentni, funkcijski jezik namijenjen za raspodijeljene pouzdane
sustave koji rade u stvarnom vremenu, najviše se koristi u telekomunikacijama
• Perl: dinamički, skriptni jezik koji je motiviran različitim alatima na operacijskim
sustavima UNIX
• Haskell: čisti funkcijski jezik

1990. – 1999.

• Java: objektno orijentirani jezik koji se izvršava na virtualnom stroju i


prilagođen je jednostavnom korištenju mreže
• ECMAScript, JavaScript, Jscript: sktriptni programski jezici ugrađeni u
današnje preglednike
• Python: objektno orijentirani, funkcijski, dinamički programski jezik s čitljivom
sintaksom
• Ruby: dinamički, objektno orijentirani programski jezik koji se prvenstveno
koristi za izradu aplikacija na webu
• PHP: skriptni, objektno orijentirani programski jezik prvenstveno zamišljen za
izradu dinamičkih web stranica

2000. – do danas

• C#: objektno i komponentno orijentirani programski jezik


• F#: funkcijski programski jezik temeljen na jeziku ML
• Groovy: dinamički, objektno orijentirani skriptni jezik
• Scala: objektno orijentirani, funkcijski programski jezik u koji su ugrađene
ideje iz Petrijevih mreža
• Clojure: funkcijski, konkurentni jezik temeljen na Lispu

Na slici 1.1 prikazan je dijagram na kojem se vidi kako su se programski jezici


razvijali. Zatamnjeni jezici su oni koji su interesantni s motrišta konkurentnosti.
PROGRAMSKI JEZICI – KONKURENTNOST I PARALELIZAM 6

Slika 1.1 – Dijagram razvoja programskih jezika

1.2 Vrste jezika

Programski jezici razlikuju se po skupu postupaka, koncepata i načela za koje je


utvrđeno da pomažu učinkovitom rješavanju neke vrste problema. Različiti su pristupi
klasifikaciji programskih jezika.

Razlikovanje jezika s obzirom na strukturu programa, strukturu stanja i metodologiju


procesiranja je ovakvo:

• Blok strukturirani, procedurno orijentirani jezici (Pascal, C)


• Objektno zasnovani, objektno orijentirani jezici (C++, Java)
• Raspodijeljeni, konkurentni jezici (Erlang, Java)
• Funkcijski jezici (Lisp, Clojure, F#)
• Logički jezici (Prolog).

Uža kategorizacija jezika je sljedeća:

• Imperativni jezici (procedurni)


• blok strukturirani i objektno zasnovani
• program je prikazan slijedom naredbi za promjenu stanja koje se
odvija od početka prema kraju
• jezik (program) modelira arhitekturu računala
• specificira se KAKO treba raditi!
PROGRAMSKI JEZICI – KONKURENTNOST I PARALELIZAM 7

• Deklarativni jezici (neprocedurni)


• funkcijski i logički
• specificira se ŠTO treba raditi, neovisno o tome kako će se izvesti!

Kad je riječ o stvarnom vremenu "kako" ili "što" nisu dovoljni. Ispravnost programa ne
ovisi samo o točnosti rezultata nego i o tome "kada" se rezultat pojavio. Vremenska
ograničenja, uz pouzdanost i mogućnost kontrole sklopovskih jedinica, postaju važna
za takve programske jezike. U telekomunikacijama stvarnovremeni su imperativni
jezici PLEX i CHILL. Primjer deklarativnog jezika za stvarnovremensko upravljanje je
Erlang.

Detaljnije klasifikacije programskih jezika nisu jednoznačne. Jedan je razlog različitost


pristupa klasifikaciji, a drugi što mnogi jezici nisu "čisti" već se temelje na više načela.
Npr. Java je objektno orijentirani, konkurentni i raspodijeljeni jezik, a Erlang
deklarativni konkurentni jezik.

Glede modela procesiranja promatra se trodimenzionalni prostor razvoja jezika:

Promjena stanja

• slijed promjena stanja izvodi se izvršavanjem naredbi: pohrani/dohvati,


upiši/pročitaj su destruktivne operacije,
• naredba određena ulaznim simbolom i tekućim stanjem (Turing),
• računala s pohranjenim programom spremaju instrukciju kao dio njenog stanja
i dovode je u procesnu jedinicu prije izvođenja,
• za asemblerske i imperativne jezike promjena stanja je osnovni model.

Distribucija poruka

• komunikacija između jedinki je osnovni model procesiranja: pošalji/primi su


nedestruktivne operacije,
• komunikacijski kanal, katkad sa spremnikom, ima ulogu memorije,
• varijabla ima značenje kanala,
• jedinka je definirana komunikacijskim sučeljem, neovisno o unutarnjem stanju,
• kombinacija komunikacije i promjene stanja: programske jedinke s unutarnjim
stanjem i komunikacijskim vratima sa spremnikom poruka za vanjsko
ponašanje.

Klasifikacija

• procesiranje se promatra kao slijed koraka klasifikacije od kojih svaki služi da


ograniči rezultat, a završava kada se izvede jedan element kao konačni
rezultat,
• objektno orijentirano programiranje podržava klasifikaciju objekata u klase.
PROGRAMSKI JEZICI – KONKURENTNOST I PARALELIZAM 8

Slijedi nekoliko napomena uz koncept objektno orijentiranog programiranja:

Objekt

• kolekcija operacija koje dijele zajedničko stanje,


• operacije određuju pozive na koje objekt može odgovoriti,
• zajedničko stanje skriveno je za vanjski svijet i može mu se pristupiti samo kroz
operacije objekta,
• varijable koje određuju unutarnje stanje objekta zovu se primjerci (instance
variables), operacije postupci (methods), a skup postupaka određuje sučelje
(interface) objekta i njegovo ponašanje (behaviour),
• podaci su zaštićeni od vanjskog pristupa – zatvoreni (encapsulation).

Klasa

• podloga iz koje se može stvoriti objekt (npr. iz klase brojilo (0 – 2n-1) može se
stvoriti objekt brojilo (0 – 15)).

Nasljeđivanje (inheritance)

• korištenje klase pri definiciji nove klase (npr- iz klase brojilo (0 – 2n-1) može
se izvesti nova klasa brojilo (-2n-1 – 2n-1-1)).

Ova tri modela procesiranja upotrebljavaju se ovako:

• promjena stanja: računanje u objektima,


• poruke: komunikacija među objektima,
• klasifikacija: upravljanje objektima i klasama.

Glede procesiranja izvornog programa razlikuju se dva postupka koji započinju


njegovim učitavanjem, analizom i provjerom ispravnosti:

• interpretiranje, kojim se ustanovljena naredba izravno izvodi te


• prevođenje, kojim se referenciranjem na skup strojnih instrukcija pridruženih
izvornoj instrukciji generira prevedeni program i povezuje sa skupom strojnih
instrukcija u biblioteci kako bi se dobio izvršni binarni program.

Imperativni jezici se u pravilu prevode (primjer za interpretiranje je Postscript), a


deklarativni interpretiraju. Zbog toga su imperativni jezici mnogo brži u izvedbi.
Sporost deklarativnih jezika ograničavala je njihovu industrijsku primjenu, a tek su
rješenja novih funkcijskih jezika kakav je Erlang dovela do prihvatljive vremenske i
prostorne složenosti. Spomenimo da je jezik Erlang isto tako započeo s
interpretiranjem, da bi se kasnije prešlo na generiranje C makro-naredbi i njihovo
prevođenje.

1.3 Značajke jezika s motrišta primjene

Značajke programskih jezika glede primjene su:


PROGRAMSKI JEZICI – KONKURENTNOST I PARALELIZAM 9

Lakoća pisanja (writability)


• ograničena kod imperativnih jezika, jer modeliraju rad procesora (izvođenje
programa),
• od početka bolja kod deklarativnih jezika (zbog potreba područja umjetne
inteligencije zbog kojeg su nastali).

Lakoća čitanja (readability)


• važna kod velikih programskih sustava čemu je obraćena pažnja u PLEX-u i
CHILL-u,
• deklarativni jezici su općenito lošiji za čitanje za što je razlog način primjene,
a ne sam jezik.

Kvalitativna usporedba jezika s obzirom na prethodne značajke slijedi: imperativni


jezici su u pravilu teži za pisanje, a lakši za čitanje, od deklarativnih jezika koji su
lakši za pisanje, a teški za čitanje.

Kod jezika za industrijske primjene nastoji se popraviti čitljivost koja je jako važna za
održavanje i evoluciju programskih proizvoda.

1.4 Konkurentnost i paralelizam u programskim jezicima

Konkurentnost označava mogućnost istodobnog izvođenja dvije ili više aktivnosti. Dvije
ili više aktivnosti se mogu izvesti istodobno ako ne utječu jedna na drugu.
Paralelizam označava istodobno izvođenje dvije ili više aktivnosti. Konkurentne
aktivnosti se mogu izvoditi paralelno ukoliko se raspolaže resursima za to.
Konkurentne aktivnosti se mogu odvijati i slijedno.
Konkurentni i paralelni jezici sadrže eksplicitne konstrukte za paralelnu izvedbu
dijelova programa čime se postiže:
• mogućnost iskorištavanja inherentnog paralelizma u problemu koji se rješava,
• povećana učinkovitost mogućom podjelom poslova u višeprocesorskom sustavu,
sustavu s višejezgrenim procesorom i mreži procesora,
• izravna potpora raspodijeljenim aplikacijama koje se moraju izvoditi u mreži.

O paralelizmu se može govoriti na dva načina: pseudoparalelizam opisuje situaciju


kad se raspolaže jednim procesorom, a dijelovi programa se "bore" za dodjelu
vremena za izvedbu. Stvarni paralelizam postiže se samo s više procesora koji
istodobno izvode različite dijelove programa. Termin konkurentnost opisuje situacije u
kojima se dijelovi programa mogu izvesti istodobno, za razliku od paralelizma koji
znači da se dijelovi programa izvode istodobno. Kod izvedbe programa u mreži
procesora riječ je o raspodijeljenom programiranju.
PROCESORI I UPRAVLJAČKI SUSTAVI 10

3. PROCESORI I UPRAVLJAČKI SUSTAVI

1.5 Razine upravljanja

U svakom komunikacijskom sustavu mogu se ustanoviti tri generičke skupine funkcija (sl.
3.1):

• F1: operacije s informacijskim tokovima,


• F2: obrada poziva i usluga,
• F3: upravljanje čvorom i mrežom.

UPRAVLJANJE
MREŽOM
resursi resursi
korisnika mreže

«INTELIGENCIJA»
OBRADA POZIVA I
upravljački USLUGA upravljački
informacijski informacijski
tok tok

OPERACIJE S
INFORMACIJSKIM
korisnički TOKOM korisnički
informacijski informacijski
tok tok

Slika 3.1 - Generičke funkcije telekomunikacijskog sustava

Funkcije iz skupine F1 su najjednostavnije, a izvode se s najvećom učestalosti (npr. za


svaki bit, znak, poruku, paket, …). Zatim slijede funkcije iz skupine F2 koje su
složenije, ali s nižom učestalosti od onih iz F1 (npr. za svaki poziv, uslugu, ….).
Najsloženije su funkcije iz skupine F3 koje su ujedno najmanje učestalosti (npr. pri
promjeni prometnih uvjeta, pojavi kvara, …). Treba uočiti da funkcijska složenost
raste od F1 prema F3, a učestalost izvođenja pada od F1 prema F3.

Uz uobičajenu podjelu na često izvođene jednostavne funkcije i rjeđe izvođene


složene funkcije potrebno je preciznije definirati kriterije funkcijske dekompozicije za
telekomunikacijske procesorske sustave. Vertikalnom dekompozicijom u svakoj funkciji
mogu se ustanoviti.

• dijelovi koji se rješavaju sklopovski (F1),


• dijelovi kojima se programski rješava neposredno upravljanje sklopovskim
jedinicama što je značajno za prihvat/dostavu podataka u stvarnom vremenu
(F1) i neposredno upravljanje funkcijom (F2),
• dijelovi kojima se programski rješava globalno upravljanje funkcijom (F2 i F3).
PROCESORI I UPRAVLJAČKI SUSTAVI 11

Svakoj razini F1 – F3 može se dodijeliti poseban procesor ili dvije (F1 i F2 ili F2 i F3)
odnosno sve tri riješiti istim procesorom (F1, F2 i F3). U sustavima manjeg kapaciteta
može se primijeniti združivanje neposrednog i globalnog upravljanja funkcijom (F1, F2
i F3 ili F2 i F3), dok se to rjeđe radi u sustavima velikog kapaciteta. Isto tako moguće
je istim procesorom neposrednog upravljanja podržati više funkcija (tzv. regionalni
procesor za F2), a u procesoru globalnog upravljanja više funkcija ili sve funkcije (tzv.
centralni procesor za F2 i F3).

1.6 Modeli procesorskih sustava

Osnovnim modelom procesora smatra se von Neumannov model. On se zasniva na


memoriji s pohranjenim instrukcijama i podacima, te tokom operacija na temelju
pojedinačne točke upravljanja opisane programskim brojilom (sl. 3.2). Njegove
značajke određuju:

• programsko brojilo koje označava instrukciju u izvođenju,


• adresa koja opisuje podatak i pristup podatku.

memorija
programa
TOČKA
UPRAVLJANJA

programsko instrukcija i
brojilo instrukcija j
instrukcija k

memorija
podataka

podatak a
podatak b ADRESA
podatak c

Slika 3.2 - Osnovni model procesiranja

Ograničenje modela je slijedno izvođenje instrukcija (jedna po jedna u slijedu) koje se


odražava na brzinu rada i informacijski kapacitet. Neka rješenja za njegovo
prevladavanje su:

• uvođenje konkurentnosti oko točke upravljanja (istovremeno izvođenje različitih


faza "susjednih" instrukcija),
• operacije nad skupom podataka (npr. vektor ili polje),
PROCESORI I UPRAVLJAČKI SUSTAVI 12

• višeprocesorska organizacija (više povezanih istovrsnih ili raznovrsnih


procesora),
• primjena optimizirajućih prevoditelja za konvencionalne (nekonkurentne)
jezike,
• uvođenje konkurentnih dijalekata konvencionalnih jezika,
• uvođenje konkurentnih jezika.

Pritom je najčešće riječ o modifikaciji von Neumannovog modela. Napuštanjem


osnovnih značajki modela (programsko brojilo, adresiranje) izvode se tzv. non von
Neumannovi modeli.

Sustavni prikaz arhitektura procesorskih sustava zasniva se na dva načela:

• broj instrukcija i podataka istodobno u obradi (Flyn),


• funkcionalnost i tok informacija (Skillicorn).

Po Flynu to su modeli procesora u kojima se obrađuje istodobno:

• jedna instrukcija - jedan podatak, SISD (Single Instruction Single Data),


• jedna instrukcija - više podataka, SIMD (Single Instruction Multiple Data),
• više instrukcija - jedan podatak, MISD (Multiple Instruction Single Data),
• više instrukcija - više podataka, MIMD (Multiple Instruction Multiple Data).

Za upravljačke primjene u stvarnom vremenu važne su arhitekture SISD i MIMD.

Skillicornova metodologija zanimljiva je, jer uvodi apstraktne modele slične onima koji
se primjenjuju u procesorskom upravljanju telekomunikacijskim sustavima. Funkcijske
jedinice apstraktnog procesora su:

• instrukcijski procesor (IP),


• instrukcijska memorija (IM),
• podatkovni procesor (DP),
• podatkovna memorija (DM),
• komunikacijska jedinica, izvedena kao komutacijsko polje ili mreža (SW n x n)
koja omogućuje povezivanje svake jedinice sa svakom.

Informacije koje izmjenjuju su:

• adresa instrukcije,
• instrukcija,
• adresa podatka
• podatak (operand, rezultat) i
• stanje operacije.

Na Skillicornovom prikazu von Neumannovog modela (sl. 3.3) označen je i redoslijed


operacija:

1. programsko brojilo u IP postavlja adresu instrukcije za IM


2. instrukcija se očitava iz IM i priprema za izvršenje u IP
3. IP dostavlja operacijski kod (OP kod) instrukcije i adrese operanada u DP
PROCESORI I UPRAVLJAČKI SUSTAVI 13

4. DP postavlja adresu operanda za DM


5. vrijednost operanda očitava se iz DM, dovodi u DP i izvodi operacija
6. DP postavlja adresu rezultata za DM
7. DP pohranjuje vrijednost rezultata u DM
8. DP vraća stanje operacije za IP čime označava da je završio izvođenje
instrukcije.

3
DP 8 IP

4 5

1 2

6 7

DM IM

Slika 3.3 - Von Neumannov model

Moguće ubrzanje rada postiže se uz:

1. Istodobno (paralelno) izvođenje više operacija za jednu instrukciju.

U ovom primjeru DP može istodobno pohranjivati rezultat operacije u DM (7) i


vraćati stanje operacije u IP (8). Time se umjesto u osam koraka izvršenje jedne
instrukcije provodi u sedam koraka. Preduvjet su odvojeni putovi kojima se to
provodi, ali nisu potrebni višestruki resursi, jer je u svakom trenutku zaposlena samo
jedna funkcijska jedinica.

2. Istodobna obrada više instrukcija u različitim fazama.

Obradu se može ubrzati tako da se istodobno izvodi više instrukcija, svaka u


drugoj fazi obrade. Tako npr. dok se jedna instrukcija dostavlja na izvođenje (3),
može se početi priprema za dohvaćanje sljedeće instrukcije u slijedu (1). Ovakav
način rada također ne traži višestruke resurse već upravljačke mehanizme kojima
će se ostvariti protok instrukcija (pipelining). U svakom trenutku može biti
zaposleno više jedinica, ali svaka s drugom instrukcijom.

3. Povećanje broja procesora.

Povećanje broja procesora ili nekih njihovih funkcijskih jedinica otvara mogućnost
paralelnog izvođenja instrukcija i samim time istodobnog obavljanja više
programa. Tipična rješenja su sljedeća:

a) Procesor polja podataka (Array Processor) koji sadrži jedan IP i više DP-ova.
Instrukcije u takvom procesoru određuju operacije sa skupovima podataka
(npr. množenje vektora ili matrica) koje se izvode tako da se međusobno
PROCESORI I UPRAVLJAČKI SUSTAVI 14

neovisna računanja provode istodobno, a za što treba više DP-ova. Ovakve


arhitekture nisu zanimljive za upravljanje komunikacijskim procesima.
b) Višeprocesorski sustav (Multiprocessor) s više međusobno povezanih osnovnih
procesora.
c) Procesor toka podataka (Data Flow Processor) koji nema IP i IM, a svu obradu
provodi više DP-a.

Višeprocesorski sustav i procesor toka podataka se primjenjuju u telekomunikacijama.

Pokažimo operacije u procesoru zasnovanom na izvornom von Neumannovom načelu


te modifikacije koje omogućuju paralelizam operacija (sl. 3.4).

IM IM

adresa traži prima


instrukcije instrukciju istrukciju

prima instrukcija instrukcija


stanje (adresa pod.) (OP kod)

DP DP DP

a) Operacije s instrukcijama

IP IP DM

prima instr. prima instr. traži


(OP kod) (adresa pod.) podatak

prima DM
podatak

sprema vraća izvršava


rezultat stanje operaciju

DM IP

vraća
stanje

IP

sprema
rezultat

DM

b) Operacije s podacima
Slika 3.4 – Operacije u von Neumannovom procesoru
PROCESORI I UPRAVLJAČKI SUSTAVI 15

Višeprocesorski sustav prikazan je na sl. 3.5 u inačici slabe povezanosti (loosely


coupled). Slaba povezanost označava da svaki DP pristupa samo svojoj DM, a
komunikacijom između DP-ova obavlja se izmjena podataka i usklađivanje rada. Jako
povezani (tightly coupled) procesori su oni u kojima svaki DP može pristupiti svakoj
DM, a ne samo vlastitoj (zajednička ili dijeljena memorija).

Slabo povezani sustav prikladan je za izvedbu mreže procesora koji međusobno


komuniciraju izmjenom podataka kroz komunikacijsku mrežu.

SW
nxn DP IP

DM IM

Slika 3.5 - Višeprocesorski sustav - slabo povezani

Procesor toka podataka je alternativni model von Neumannovom koji ne rabi


programsko brojilo niti adresiranje podataka. Ne postoji mehanizam slijednog
izvođenja instrukcija kakav određuje programsko brojilo, već se u svakom trenutku
mogu izvesti sve instrukcije za koje su podaci prisutni. Druga je značajka toka
podataka da se pri izvođenju instrukcija rukuje izravno s vrijednostima podataka
umjesto s njihovim adresama. Kod takvog rješenja se instrukcija tj. operacijski kod i
vrijednosti podataka stapaju u jedinstvenu "instrukcijsku ćeliju" koja se smješta u
memoriju podataka. U ćeliji se nalaze indikatori prisutnosti podataka koji označavaju
uvjete kada se instrukcijska ćelija može izvesti.

Jako povezani procesor toka podataka predočen je na sl. 3.6.

SW
nxn DP

DM

Slika 3.6 - Procesor toka podataka – jako povezani


PROCESORI I UPRAVLJAČKI SUSTAVI 16

Pokažimo tok podatka na jednostavnom primjeru. Program treba riješiti sljedeće:

A=2
B=A+6
C=A–1
D=B+C

Von Neumannov model bi navedene instrukcije izvodio jednu po jednu, točno u slijedu
u kojem su napisane i smještene u memoriju. Procesor toka podataka će formirati
instrukcijske ćelije i ustanoviti da su, nakon što se odredi vrijednost podataka A,
prisutni svi podaci potrebni za izvedbu operacija kojima se izračunavaju B i C. Te se
dvije ćelije, ako postoje dva DP, izvode istodobno i time skraćuje vrijeme ukupno
vrijeme obrade (sl. 3.7).

operacijski kod I1 I2
podatak 1
podatak 2

+ 1
A=2
A 2
6 B=8
B 8 +
B 8
C 1 D=9
- 1 D 9
A 2
1 C=1
C 1

Slika 3.7 – Primjer obrade instrukcijskih ćelija

Tok podataka se, osim na razini instrukcija, može primijeniti i na višoj razini - pri
izvođenju programskih modula. U takovim arhitekturama DP-ovi su funkcijski različiti
procesni elementi za izvođenje programa (“instrukcijski procesori”), za
međuprogramsku komunikaciju (“signalni procesori”), za vanjsku komunikaciju (“UI
procesori”), za dostup bazi podataka i drugo.
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 17

4. PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM


PORUKA

1.7 Predočavanje paralelizma u programskim jezicima

Četiri osnovna načina predočavanja paralelizma u programskim jezicima su korutine,


paralelne naredbe, procesi i niti. U daljoj razradi koncepti će se najčešće ilustrirati
rješenjima i primjerima iz jezika Erlang i Java.

1.7.1 Korutine

Korutinama (co-routine) se nazivaju dijelovi programa koji se izvode preklapajući se u


vremenu (sl. 4.1). U svakoj točki tijekom izvedbe jedna korutina može obustaviti ili
ponovno pokrenuti drugu. Ponovno pokrenuta korutina nastavlja se od točke u kojoj je
bila obustavljena.

prog. 1

prog. 2

Slika 4.1 – Korutine

Ovakav način izvođenja naziva se pseudoparalelnim, jer se u jednom trenutku izvodi


samo jedna korutina, ali promatrač izvana dobiva dojam kako se dvije ili više
korutina izvode istodobno.

Jezici Erlang i Java ne rade s korutinama.

1.7.2 Paralelne naredbe

Paralelne naredbe izvode se istodobno, što se postiže paralelizacijom uobičajenih


programskih konstrukata, npr. bloka (parbegin - parend) ili petlje (parfor) (sl. 4.2). Sve
naredbe paralelnog bloka izvode se istodobno, kao i sve iteracije paralelne petlje.
Paralelizam razine naredbe naziva se fino-granuliranim paralelizmom (fine-grain).
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 18

parbegin
1
2
3 istodobno
4
parend

1 2 3 4

Slika 4.2 – Paralelne naredbe

Paralelne naredbe su važnije za numeričke probleme tako da ih jezici Erlang i Java


ne sadrže.

1.7.3 Proces

Proces je najpotpuniji i najčešći konstrukt za predočavanje paralelizma (sl. 4.3).


Proces je apstrakcija fizikalnog procesora. On izvodi naredbe u slijedu, ima svoje
stanje i podatke. Paralelizam se postiže s više procesa koji se izvode na više
procesora i međusobno komuniciraju.
Broj procesa je u većini jezika varijabilan. Obično se prvo definira procesni tip
procesa koji opisuje programski kod koji taj proces izvodi. Zatim se više procesa tog
tipa stvara odvajanjem od osnovnog procesa (fork) i oba procesa nastavljaju s
paralelnim radom. Procese treba uskladiti (sinkronizirati). Najjednostavnije je rješenje
s čekanjem osnovnog procesa da svi završe obradu i sastanu se (join). Kako procesi
izmjenjuju informacije, sinkronizacija se ne može promatrati odvojeno od komunikacije.
proces

naredbe

podaci

Slika 4.3 – Proces

Jezik Erlang radi samo s procesima. Proces u Erlangu definiran je kao samostalna
obradna jedinka koja postoji konkurentno s drugim procesima u sustavu pri čemu ne
postoji nikakva hijerarhija procesa osim one koju uvede programer. Program u jeziku
Erlang sastavljen je od modula koji sadrže procese, a ovi funkcije. Novi paralelni
proces pokreće se funkcijom spawn koja ujedno vraća identifikator procesa. Oblik
naredbe je ovakav:
Pid = spawn(Module, FunctionName, ArgumentList)

Pid - identifikator procesa,


Module - naziv modula u kojem se nalazi proces,
FunctionName - naziv funkcije,
ArgumentList - argumenti.
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 19

Primjerice proces Pid1 će kreirati proces Pid2, oba će se izvoditi konkurentno, a


identifikator Pid2 bit će poznat samo procesu Pid1 (sl. 4.4). Kako je identifikator
procesa potreban za komunikaciju s njim, spriječena su neželjena međudjelovanja
procesa. To je element sigurnosti jezika Erlang kojim se onemogućuje rasprostiranje
identifikatora procesa.
Proces Pid1

Pid2 = spawn(Mod, Fun, Arg)

Proces Pid1 Proces Pid2

Pid2 = spawn(Mod, Fun, Arg)

Slika 4.4 - Stvaranje paralelnog procesa u jeziku Erlang

1.7.4 Nit

Nit ili dretva (thread) razlikuje se od procesa jer ne raspolaže vlastitim podacima, već
ih dijeli s procesima i drugim nitima. Nit izvodi naredbe u slijedu, ima svoje stanje
obrade, ali ne i podatke (sl. 4.5).
proces nit (dretva)

naredbe

podaci

Slika 4.5 – Nit

Jezik Java radi s nitima.

1.7.5 Komunikacija i sinkronizacija

Komunikacija između programskih jedinki ostvaruje se zajedničkim podacima koje one


dijele (shared data) ili izmjenom poruka (message passing).

Zajednički podaci za programske jedinke imaju isto značenje kao zajednička


memorija za jako povezane procesore. Svaki program (procesor) može dohvatiti
svaki podatak (memoriju). Izmjena poruka između programa ima isto značenje kao
komunikacija između procesora u slabo povezanom sustavu ili mreži: svaki program
(procesor) pristupa samo svojim podacima (memoriji),a razmjenjuju one podatke koji
su im potrebni za suradnju pri obavljanju poslova.
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 20

1.7.6 Zajednička varijabla

Varijabla čiji naziv zna više procesa, a koja služi za izmjenu informacije između
procesa naziva se zajedničkom varijablom i primjenjuje se za jednoprocesorski sustav
i višeprocesorski sa zajedničkom memorijom.

Za dva procesa koji se služe zajedničkom varijablom tipičan je odnos


potrošač/proizvođač (consumer/producer). Usklađivanje njihovog rada postiže se na
dva načina:
• uvjetnom sinkronizacijom kod koje čekaju jedan na drugoga da završe čitanje
ili upisivanje (conditional synchronization),
• međusobnim isključivanjem kojim će se spriječiti da oba rade istodobno sa
zajedničkom varijablom (mutual exclusion synchronization).

Dijelovi procesa u kojima se pristupa zajedničkim varijablama nazivaju se kritičnim


dijelovima (critical section). U njima može doći do neregularnosti glede operacija s
varijablom kao zajedničkim resursom.

Kako se u telekomunikacijama radi sa slabo povezanim procesorima (bez zajedničke


memorije) i slabo povezanim procesima (bez zajedničkih podataka) sinkronizacijski
primitivi za zajedničke varijable će se samo pobrojiti:

• varijabla brava (lock variable) je zajednička varijabla s dvije operacije:


zaključaj - otključaj (lock - unclock); kad jedan proces zaključa drugi ne može
pristupiti dok ponovno ne otključa
• semafor (semaphore) je zajednička varijabla s dvije operacije: povećaj za 1 -
smanji za 1 ako vrijednost nije nula (V(s) - P(s)); operacijom V(s) proces
onemogućuje pristup drugima, a proces koji uspije s operacijom P(s) nastavlja s
obradom.
• monitor je apstraktni tip podatka u kojem su zatvoreni podaci i operacije nad
njima, a samo jedan proces može izvoditi operaciju. S dvije operacije može
se blokirati i reaktivirati blokirani proces (wait, signal). Kad je proces u
monitoru siguran je da je jedini aktivan pa to mora trajati kratko kako bi i
ostalima dopustio izvedbu.

1.7.7 Izmjena poruka

Svaki proces ima svoje varijable kojima ne mogu pristupiti drugi procesi, a potrebne
podatke izmjenjuju porukama. Problemi koje su donijele zajedničke varijable su
izbjegnuti, ali su se pojavili novi. Kako nema zajedničke informacije o stanju, pojavio
se bitan nedostatak - procesi ne raspolažu istom informacijom o stvarnom vremenu
(takt, vremensko usklađivanje, dogovor o vremenu).

Osnovni model izmjene poruka sadržan je u konceptu komunicirajućih slijednih procesa


(CSP - Communicating Sequential Processes). Riječ je o dva primitiva kojima se poruka
odašilje (send) i prima (receive) ovako:

send message to R (u predajnom procesu S)


receive message from S (u prijamnom procesu R).
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 21

Predajni proces S šalje poruku, a prijamni proces R je prima. Naredbe za predaju i


prijam poruke predočuju raspodijeljeno pridruživanje (distributed assignement), a
različiti jezici dopuštaju različite strukture podataka u poruci pa tako i različita
pridruživanja.

U jeziku Erlang primitiv send je oblika:


Pid!Message

gdje je Pid identifikator procesa kojem se šalje poruka, a Message poruka.

a primitiv receive ovakav:


receive
Message1 [when Guard1] ->
Actions1 ;
Message2 [when Guard2] ->
Actions2 ;
...;
end.

Message1 i Message2 (i više njih) su uzorci za koje se provjerava podudarnost s


onim poslanim. Kad je ista ustanovljena i pripadni uvjet (Guard) ispunjen, poruka je
odabrana i izvode se predviđene akcije (Actions). receive vraća vrijednost
zadnjeg izračunatog izraza. Moguć je prijam bilo koje poruke ako se postavi uzorak
AnyMessage:
receive
AnyMessage ->
Actions ;
end.

Izmjena poruka između dva procesa predočena je slikom 4.6.


Proces Pid1 Proces Pid2
receive
Message Message ->
Pid2 ! Message
Actions;
end.

Slika 4.6 - Izmjena poruka

1.8 Međuprocesna komunikacija izmjenom poruka

Razlikuje se više vrsta izmjene poruka s obzirom na imenovanje procesa, slanje


poruka, smjer komunikacije i odnose komunicirajućih procesa.

1.8.1 Imenovanje procesa

Imenovanje predajnog i prijamnog procesa (sl. 4.7) može biti:


• simetrično izravno; svaki proces imenuje onog drugog,
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 22

• asimetrično izravno; prijamnik prima poruku od svakog procesa,


• posredno; uvodi se međuobjekt nazvan vrata (port) za izmjenu poruka od više
procesa prema jednom ili međuobjekt poštanski sandučić (mailbox) za izmjenu
poruka između više procesa.

Proces S Proces R
send to R receive from S

a) simetrično izravno

Proces S'
send to R Proces R

Proces S'' receive

send to R

b) asimetrično izravno

Proces S'
send to P Proces R

Proces S'' port P receive from P

send to P

Proces S' Proces R'


send to M receive from M
mailbox
Proces S'' M Proces R''
send to M receive from M

c) posredno imenovanje

Slika 4.7 - Imenovanje procesa

Svaki Erlang proces ima poštanski sandučić u koji se poruke slažu redoslijedom
nailaska. Jedan proces šalje poruku drugom procesu tako da je smješta u njegov
poštanski sandučić. Proces prima poruku od drugog procesa tako da je nastoji izvaditi
iz sandučića. Ako to ne uspije, obustavlja proces do sljedeće poruke, a neprimljenu
poruku ostavlja za kasniju obradu. Kako poruke koje se ne mogu primiti ostaju u
sandučiću, programer se mora brinuti za njegovu popunjenost.

Iako se rabi poštanski sandučić postignuto je izravno imenovanje procesa. Osnovni


oblik send i receive primitiva opisuje asimetrično izravno imenovanje kojim predajni
proces imenuje identifikatorom prijamni proces, dok prijamni proces ne zna za
predajni.

Moguće je i simetrično izravno imenovanje za prijam poruke od nekog određenog


procesa.
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 23

U slanje poruke tada treba uključiti identifikaciju predajnog procesa funkcijom self():
Pid!{self(),abc},

kao i u prijam:
receive
{From, Message} ->
Actions;
end.

1.8.2 Suradnja procesa

Suradnja procesa određuje odnose jednog ili više predajnih procesa s jednim ili više
prijemnih procesa. Moguće su sljedeće situacije x:y za x predajnih i y prijamnih
procesa:

1:1

• izmjena poruka između dva procesa u jednoj točki: kanal između procesa,
izravno imenovanje procesa,
• izmjena više poruka u više točaka: više kanala - izravno imenovanje kanala ili
poštanski sandučić (sl. 4.8).

S R

Slika 4.8 – Izmjena triju poruka u tri točke: tri kanala, izravno

n:1

• više procesa šalje poruke jednom procesu: asimetrično imenovanje procesa

1:m

• jedan od prijamnih procesa prima poruku (anycast),


• svi prijamni procesi primaju poruku (broadcast),
• neki od prijamnih procesa, odnosno procesi u definiranoj skupini primaju
poruku (multicast).

n:m

• svaki proces može poslati poruku svakom procesu, svaki proces može primiti
poruku od svakog procesa,

1.8.3 Način izmjene poruka


PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 24

Izmjena poruka može biti sinkrona ili asinkrona.

Sinkrona komunikacija

Komunikacija je sinkrona ako predajni proces nastavlja obradu tek nakon potvrde od
prijamnog procesa (sl. 4.9).

Spomenimo da je ta potvrda nevidljiva (za programera). Sinkrono komuniciraju


procesi u modelu CSP (imenovani kanal, jednosmjerno).

S R

send
poruka
receive
potvrda

nastavlja

Slika 4.9 – Sinkrona izmjena poruka

Asinkrona komunikacija

Kod asinkrone komunikacije predajni proces nastavlja s radom odmah nakon predaje
poruke. Da bi to bilo moguće potreban je spremnik za poruke, npr. poštanski
sandučić, u kojem će poruka biti pohranjena dok je ne preuzme prijamni proces (sl.
4.10).

Procesi u jeziku Erlang komuniciraju asinkrono.


PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 25

S R

send

odmah M
nastavlja

receive

Slika 4.10 – Asinkrona izmjena poruka

1.8.4 Način prijama poruke

Prijam može biti eksplicitni ili implicitni.

Eksplicitni je prijam onaj kod kojeg prijamni proces kontrolira i određuje kad će
primiti poruku (sl. 4.11).

R
čeka
poruku

receive

nastavlja

Slika 4.11 – Ekplicitni prijam poruke


Kod implicitnog prijama se, kad poruka stigne, na prijamnoj strani stvara novi
podproces – nit koja obavi prijam, a odvija se pseudoparalelno s osnovnim prijamnim
procesom i dijeli s njim podatke (sl. 4.12). Oni se sinkroniziraju kao u primjeru
zajedničkih varijabli.
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 26

poruka

<<t

nastavlja

receive

Slika 4.12 – Implicitni prijam poruke

U jeziku Erlang primjenjuje se eksplicitni prijam.

1.8.5 Smjer komunikacije

Međudjelovanje procesa može biti jednosmjerno ili dvosmjerno.

Jednosmjerno je pri normalnom odašiljanju i prijamu jedne poruke, a dvosmjerno kad


se u povratnom smjeru daje odgovor na primljenu poruku. To se često rabi kod
procesa u međusobnom odnosu klijent/poslužitelj (client/server).

Slijedi primjer dvosmjerne komunikacije za model klijent/poslužitelj u jeziku Erlang:

Klijent:

klijent(Zahtjev)->
server!{self(), Zahtjev},
receive
{server, Odgovor} ->
R
end.

Poslužitelj:

server(Data) ->
receive
{From, X} ->
{R,Data1}=fn(X,Data),
From!{server,Rjesenje}
server(Data1)
end.
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 27

Kako rješenje s obvezatnim poznavanjem identifikatora prijamnog procesa kao


preduvjetom za slanje poruke nije dobro za poslužiteljske procese, uvedena je
registracija procesa kojom se procesu dodjeljuje i naziv:

register(Name, Pid)

Nakon toga se umjesto identifikatora u send primitivu može upotrijebiti naziv (Name).
Spomenimo da se registracija može ukinuti po potrebi.

Zahtjev koji klijent upućuje poslužitelju mora imati identifikator klijenta da bi mu


poslužitelj mogao uzvratiti odgovorom. Odgovor mora sadržavati registrirani naziv
poslužitelja da bi klijent mogao provesti selektivni prijam (podudaranjem uzorka).

klijent(Zahtjev)->
server1!{self(), Zahtjev1},
server2!{self(), Zahtjev2},
receive
{server1, Odgovor} ->
R1;
{server2, Odgovor} ->
R2
end.

Dvosmjerna komunikacija rješava se na više načina, a to su:

• sastanak (rendezvous) koji predočuje dvosmjernu komunikaciju porukom i


potvrdom s eksplicitnim prijamom. Često se, da bi se razlikovalo jednosmjernu
od dvosmjerne komunikacije, njihovim komunikacijskim primitivima daju različiti
nazivi (npr. za odašiljanje send, return, za prijam receive, enter, accept,
retrieve). Na slici 4.13 prikazana je sinkrona dvosmjerna komunikacija uz
eksplicitni prijam. Proces X šalje zahtjev procesu Y i čeka rezultat obrade.
Nakon primitka rezultata, nastavlja s radom.

X Y

zahtjev

čeka
obrada

rezultat

Slika 4.13 – Sinkrona dvosmjerna komunikacija uz eksplicitni prijam

• poziv udaljene procedure (RPC - Remote Procedure Call) prvenstveno je


namijenjen komunikaciji između klijenta i poslužitelja na načelu implicitnog
prijama (sl. 4.14). Ideja je jednostavna i nalikuje običnom pozivu procedure,
ali je izvedba teža, jer je riječ o proceduri koja se izvodi na udaljenom
računalu. Jezik treba omogućiti procesu klijentu da sve parametre smjesti u
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 28

poruku (marshalling), a procesu poslužitelju da izvadi parametre iz poruke


(unmarshalling). Poslužitelj izvozi (export) jednu ili više procedura za udaljeno
pozivanje, a sučelje prema poslužitelju specificira procedure.

X1 Y

upit

prima
čeka poruku

odgovor

obrada

X1 upit

odgovor
čeka

Slika 4.14 – Sinkrona dvosmjerna komunikacija uz implicitni prijam

Jezik Erlang u raspodijeljenoj primjeni (više procesora ili mreža) radi s udaljenim
pozivom procedura.

1.8.6 Stvarnovremeni aspekti

Zanimaju nas jezici za stvarnovremene primjene - točnije deklarativni konkurentni


stvarnovremeni jezik kakav je Erlang te razmatranje moramo proširiti na vremenske
uvjete procesiranja, pouzdanost i rad sa sklopovskim jedinicama. U jeziku i Erlang
konkurentnost se izražava eksplicitno. Erlang je "mali" jezik kao Pascal ili C. Svi ovi
jezici nastali su u maloj razvojnoj skupini i u kratkom vremenu za razliku od "velikih"
jezika kakvi su PL/1 ili Ada. Namijenjen je programiranju velikih konkurentnih sustava
za koje su važni modularnost, otkrivanje pogrešaka te punjenje i promjena programa
bez zaustavljanja.
PROCESI I MEĐUPROCESNA KOMUNIKACIJA IZMJENOM PORUKA 29

Kao što je prije spomenuto Erlang ima procesno zasnovanu eksplicitnu konkurentnost
prema modelu SDL-a (Specification and Description Language), s asinkronom izmjenom
poruka. Konkurentnost se lako ostvaruje, procesi zahtijevaju malo memorije, a stvaraju
se i ukidaju jednostavno. Izmjena poruka isto je jednostavna.

Jezik omogućuje prijelaz iz jednoprocesorske u raspodijeljenu primjenu, jer ne rabi


zajedničku memoriju. Erlang ima dobra integracijska obilježja tako da je omogućeno
povezivanje s programima u drugim jezicima. Upravljanje memorijom riješeno je s
dinamičkim ustanovljavanjem neupotrijebljenih dijelova memorije (garbage collection)
te automatskom dodjelom i otpuštanjem memorije.

Mjerenje vremena

Jezik Erlang namijenjen je upravljanju u tzv. mekom stvarnom vremenu (soft real-time)
koje zahtijeva vrijeme odgovora u milisekundama. Primjenjuje se eksplicitno mjerenje
vremena, a točnost je ograničena operacijskim sustavom ili procesorom koji izvodi
Erlang program.

Primjer za vremenski kontrolirane operacije je proširenje prijama poruke vremenskom


kontrolom:

receive
Message1 [when Guard1] ->
Actions1 ;
Message2 [when Guard2] ->
Actions2 ;
...
after
TimeOutExpr ->
ActionT ;
end.

TimeOutExpr je izraz s vrijednošću koja odgovara milisekundnom intervalu. Ako se


ne primi poruka za vrijeme tog intervala, izvodi se ActionT.

Vremensku kontrolu se može postaviti na vrijednost 0 (vremenska kontrola ističe


odmah nakon provjere svih prijama) i infinity (vremenska kontrola neće nikada
isteći). U drugom slučaju se tijekom rada može vrijednost vremenske kontrole postaviti
na neku željenu.

Sljedeća je mogućnost vremenskog nadgledanja obustavljanje procesa za neki period


(Time), primjerice ovako:

sleep(Time)
receive
after Time ->
true
end.

Može se riješiti i automatsko pozivanje procesa nakon isteka zadanog intervala


upućivanjem poruke, kao i strukturiranje procesa koji je mjerač vremena (timer).
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 30

5. DEKLARATIVNI KONKURENTNI JEZIK ERLANG

Erlang je raspodijeljeni funkcijski konkurentni jezik za stvarnovremenske primjene.


Ciljevi pri njegovom razvoju bili su:

• za čovjeka – mali, jednostavan, učinkovit jezik (manje napora, mogućnost


uporabe s drugim jezicima),
• za program: čist, jednostavan, standardni program, izvodljiv na različitim
platformama (od jednog procesora do raspodijeljenog sustava – mreže).
• za sustav: primjene s visokim stupnjem konkurentnosti, kontinuirani rad
(zamjena programa), otpornost na pogreške.

Razvijen je u Ericsson-u 1987. godine (Computer Science Laboratories) gdje je i dobio


ime kao skraćenica od ERicsson LANGuage. Razvoj Erlanga je počeo s istraživanjima
u potrazi za programskim jezikom koji će omogućiti programiranje velikih industrijskih
sustava (npr. telekomunikacijski komutacijski sustavi). Od sredine osamdesetih do
danas, Erlang je izrastao u stabilan programski jezik koji je široko otvoren prema
Internetskim tehnologijama i drugim programskim jezicima. 1998. godine Erlang je
postao dostupan preko otvorenog koda i od tada zajednica oko Erlanga polako i
sigurno raste. Posebnu pozornost je privukao posljednjih nekoliko godina pojavom
procesora s više jezgri gdje njegova svojstva dolaze do izražaja.

Pomoću programskog jezika Erlang je napravljana programska podrška za: mrežnu


opremu, pozivne centrime, pokretnu mrežu, financijama, splet računala, poslužitelj
weba, programima za modeliranje 3D objekata, sustave poručivanja, baze
podataka i sl. Koriste ga i sveučilišta i industrija.

1.9 Osnovne karakteristike

Erlang je izgrađen na iskustvima drugih programskih jezika slične vrste koji su svoje
konkurentne karakteristike gradili na proširivanju postojećih jezika (Concurrent Pascal,
Occam i drugi). Konkurentnost je od početka ugrađena u Erlang kao i sljedeće
osobine:

Deklarativna sintaksa

Opisujemo ono što želimo da program radi, a ne naređujemo izravno. Nema


sporednih pojava (side effects). Varijabli se vrijednost može dodijeliti samo jednom
(single assignement language) čime se dobija čistiji i pouzdaniji kod, a olakšava
pronalaženje pogrešaka (debugging).

Konkurentnost

Programi u Erlangu mogu se izvoditi paralelno. Konkurentnost se gradi na procesima


koji komuniciraju izmjenom poruka. Procesi se stvaraju i ukidaju uz malo zauzeće
memorijskog prostora (light-weight processes) i mali utrošak vremena.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 31

Stvarno vrijeme

Programi u Erlangu zadovoljavaju uvjete programiranja u stvarnom vremenu (soft


real-time), a vrijeme odziva (response time) je reda veličine milisekunde.

Non-stop sustav

Omogućuje izmjenu koda bez prekidanja rada programa. Primjena u nekim


specifičnim uvjetima (npr. telefonska centrala ili kontrola zračnog prometa) ne
dozvoljava zaustavljanje programa radi promjene koda, za što Erlang ima ugrađene
mehanizme.

Robustnost

Ugrađeni su mehanizmi koje omogućuju detekciju pogreške u sustavu i zadržavaju


sustav u radu iako je došlo do pogrešaka. Erlang programi su otporni na pogreške u
radu čvorova (računala) i veza (mreža) između njih.

Upravljanje memorijom

Memorija se zauzima i oslobađa prema potrebi u stvarnom vremenu (real-time


garbage collector). Na taj način se rješavaju uobičajeni problemi sa zauzimanjem
memorije.

Raspodijeljenost

Erlang ne koristi zajedničku memoriju već svu komunikaciju između procesa obavlja
asinkronom izmjenom poruka.

Povezanost s drugim programskim jezicima

Erlang ima ugrađenu vezu prema drugim programskim jezicima kao npr. C, Java i sl.

Platforme

Postoje verzije Erlanga za Solaris, Windows, Linux, FreeBSD i Mac OS X.

1.10 Osnovni funkcijski koncepti

Funkcijska paradigma jedna od sastavnica jezika Erlang. Erlang funkcije definirane su


ovako:

function := function_clause | function_clause ";" function


function_clause := clause_head clause_guard clause_body
clause_head := "atom"("formal parameter list")
clause-guard := "when" guard | e
clause_body := "-" exprs
guard := "true" | guard_tests
atom - naziv funkcije
formal parameter list - argumenti odijeljeni zarezima
exprs - jedan ili više izraza
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 32

guard_test - jedna ili više usporedbi i poziva predefiniranih


provjera odijeljenih zarezima
e - prazno

Svaku funkciju čini jedna ili više rečenica (clause) odijeljenih s ";", a svaka pojedina
rečenica sastoji se od zaglavlja (head), opcijskog uvjeta – čuvara (guard) i tijela
(body). Da bi se izvela funkcija, uvjet treba biti ispunjen.

Pokažimo primjer funkcije za računanje faktorijela:

factorial(N) when N > 0


N1 = N -1,
F1 = factorial(N1),
N * F1.

Tijelo funkcije razvijeno je u niz poziva kako bi se ostvarila slijedna izvedba,


povećala jasnoća programa, prihvatile vrijednosti koje funkcija vraća i ponovno
uporabili rezultati funkcijskog poziva.

Funkcijski se jezici nazivaju i aplikacijskima, a počivaju na sljedećim načelima


ilustriranim primjerima iz jezika Erlang:

• osnovni konstrukt jezika je funkcija koja izračunava vrijednost ovisno o ulaznim


parametrima (argumentima)

factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).

• rekurzivne funkcije zamjenjuju petlju

factorial(N) -> N * factorial(N-1).

• funkcija prihvaća parametre različitog tipa tako da vrijedi višeobličnost

za ugrađene funkcije u Erlangu (BIF - Built In Function) to ne vrijedi.

• funkcije višeg reda upotrebljavaju funkcije (nižeg reda) kao argumente


(jednako se tretiraju varijable i funkcije)

ne rabi se u Erlangu.

Logika

Sinatksa Erlang funkcije bliska je sintaksi logičkog pravila.

Vrste podataka

Najvažnija struktura podataka je lista:

[1, 2, a, b] pohranjuje promjenjivi broj jedinki (u ovom primjeru ih je 4).


DEKLARATIVNI KONKURENTNI JEZIK ERLANG 33

Koncepti tipizacije su različiti i ovisni o jeziku: slaba tipizacija (Lisp nema tipova),
implicitno jaka tipizacija (kod Mirande tipove deklarira korisnik), Erlang nema
korisničke definicije tipa. Tipovi podataka u Erlangu su:

konstantni tipovi broj (cijeli, realni), atom (konstanta s nazivom),


identifikator procesa, referenca
složeni tipovi n-torka (fiksni broj članova), lista (promjenjivi broj članova).

Pridruživanje

U jeziku Erlang podudarnost uzorka upotrebljava se za dodjelu vrijednosti varijabli i


kontrolu toka programa (odabir funkcije).

Erlang je jezik s jednostrukim pridruživanjem (single assignment) što znači da se


jednom dodijeljena vrijednost varijabli više ne može promijeniti. Podudaranje uzorka
upotrebljava se za pronalaženje izraza jednakog uzorku. Ako su uzorak i izraz isti
podudarnost je ustanovljena i sve varijable u uzorku postavljaju se na vrijednost
podatka na istom položaju u izrazu (expression). Zapis:

pattern = expression

izaziva izračunavanje izraza i ispitivanje podudarnosti s uzorkom; ako je postignuta,


sve varijable dobivaju vrijednost, odnosno po Erlang terminologiji postaju ograničene
(bounded), kao primjerice:

SMJER = {lijevo, desno}


{X, Y} = SMJER

X dobiva vrijednost lijevo, a Y vrijednost desno.

Druga je namjena uzorka pri pozivanju funkcija – tada se ispituje podudarnost


argumenta u funkcijskom pozivu s uzorkom u definiciji funkcije. Pronalaženje izaziva
izračunavanje funkcije, kao na primjer:

funkcija:
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1)

poziv:
factorial(0)

rezultat:
1.

Erlang je jezik bez složenih konstrukta kao što su djelomična parametrizacija, funkcije
višeg reda i lijeno izračunavanje.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 34

1.11 Aritmetički izrazi


Aritmetički izrazi se sastoje od operatora i jednog ili dva argumenta (tablica 5.1).
Oni imaju prioritete pomoću kojih se mogu kombinirati. Ako želimo promijeniti
redoslijed računanja izraza onda moramo koristiti zagrade.

Tablica 5.1 – Aritmetički izrazi


Operator Opis Vrsta Prioritet
podatka
+X pozitivan X broj
1
-X negativan X broj
X * Y množenje X i Y broj
X / Y dijeljenje X sa Y broj
bnot X operacija NE nad bitovima cijeli broj
X div Y cjelobrojno dijeljenje X sa Y cijeli broj 2
X rem Y ostatak cjelobrojnog dijeljenja X sa Y cijeli broj
X band operacija I između bitova brojeva X i Y cijeli broj
Y
X + Y zbrajanje X i Y broj
X – Y oduzimanje od X-a Y broj
X bor Y operacija ILI između bitova brojeva X i Y cijeli broj
X bxor operacija EX-ILI između bitova brojeva X i Y cijeli broj 3
Y
X bsl N aritmetičko pomicanje bitova broja X za N cijeli broj
mjesta u lijevo
X bsr N pomicanje bitova broja X za N mjesta u desno cijeli broj

1.12 Slijedno programiranje

1.12.1 Vrste podataka u Erlangu

Vrste podataka u jeziku Erlang mogu se podijeliti u dvije skupine:

• osnovne vrste,
• izvedene vrste.

Osnovne vrste su brojevi (numbers) i atomi (atoms).

Brojevi

Brojevi predstavljaju cijele i realne brojeve i mogu imati sljedeći oblik: 455, -345,
4.454345, 7.8e22, -1.2e-22.

Atomi

Atomi su tekstualna polja zatvorena u jednostruke navodnike (ako sadrže bjeline).


Primjer atoma: proba, atom_a, atom_b, ‘ovo je atom, takodjer’. Atom, ako
nije zatvoren u navodnike, ne smije početi velikim slovom.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 35

Izvedene vrste su n-torke (Tuples) i liste (Lists).

N-torke

N-torke sadrže fiksni broj elemenata i ograđene su vitičastim zagradama. Više o


funkcijama s n-torkama nalazi se u modulu erlang. Primjer:

{1, jedan_atom, ‘Jedan_atom’}


{jedan,dva,tri}
{}
{prazno}

Liste

Liste su slične n-torkama i za razliku od njih nisu fiksne duljine, što znači da tijekom
izvršavanja programa mogu mijenjati svoju duljinu spajanjem ili dijeljenjem. Liste su
ograđene uglatim zagradama. Primjer:

[{prvi_element},{drugi_element},treci_element,4]

Liste se mogu koristiti za organiziranje i složenijih struktura, jer elementi listi mogu biti
liste i n-torke. Funkcije koje manipuliraju listama nalaze se u modulu lists. U Erlangu
zapravo ne postoje stringovi jer se oni zapisuju kao liste brojeva gdje svaki znak
jedan broj. Primjer:

Ime=“ILJ“.

Varijabla Ime je zapravo lista [73,76,74]. Znakovi se kodiraju standardom ISO-


8859-1 odnosno Latin-1. Kod nekog slova možemo dobiti pomoću znaka $ npr. kod
za slovo I možemo dobiti pomoću izraza $I. Funkcije koje manipuliraju stringovima se
nalaze u modulu string.

Binarni podaci

Binarni podaci (binaries) su vrsta podataka koja služi za spremanje velikih količina
„sirovih“ podataka. Ova vrsta podataka je prostorno efikasnija od lista i n-torki.
Virtualni stroj efikasnije (brže) manipulira takvim podacima. Ovo je pogodno za
implementaciju različitih protokola na nižim slojevima koji manipuliraju podacima na
razini bita. Binarni podaci ograđuju se znakovima << s lijeve strane i >> s desne
strane. Unutar tih ograda zapisuju se elementi koji su odvojeni zarezima. Svaki
element mora imati vrijednost, a ostali elementi nisu obavezni. Format elementa je:

E = Value:Size/TypeSpecifierList

Value je broj, Size je broj bitova, a TypeSpecifierList predstavlja dodatne


opcije. Ukupan broj bitova binarnog podatka mora biti djeljiv s 8. Primjer:

<<5,10,45>> - u prvih 8 bitova je zapisan broj 5, u sljedećih 8 je broj


10, itd.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 36

<<X:5, Y:6, Z:5>> - u prvih 5 bitova je zapisan broj X, u sljedećih 6 je Y i u


sljedećih 5 je Z.

Zapisi

Prilikom korištenja n-torki s velikim brojem elemenata nastaje problem korištenja


pojedinih elemenata i lako dolazi do pogrešaka. Da bi se to izbjeglo uvedeni zapisi
(records). Zapisi omogućuju imenovanje elemenata u n-torkama. Zapisi se definiraju ili
u modul.erl datoteci gdje se definira i modul ili u posebnoj datoteci x.hrl koji se
može uključiti u svaki modul. Format zapisa je sljedeći:

-record(point, {x=0, y=0, comment}).

Ovdje je definiran zapis pod imenom point koji se sastoji od 3 elementa. Prvi je
pod nazovom x i ima pretpostavljenu vrijednost 0 (ako nije naveden prilikom
korištenja), drugi je y koji isto tako ima pretpostavljenu vrijednost 0, a treći je pod
imenom comment koji nema pretpostavljenu vrijednost. Primjer korištenja:

P1=#point{x=2, comment=“komentar“}
#point{x=X, y=Y, comment=Comment}=P1

U prvom retku smo definirali varijablu P1 koji je vrste zapisa point i elementu x je
dodijeljena vrijednost 2, elementu y vrijednost 0 (pretpostavljena vrijednost), a
elementu comment vrijednost “komentar“. U drugom retku se podudaraju uzorci
(vidi 1.12.3) i varijabli X dodjeljuje vrijednost 2, varijabli Y dodjeljuje 0, a varijabli
Comment vrijednost “komentar“. Ako želimo samo jedan element dohvatiti onda
možemo korisiti sintaksu s točkom kako slijedi.

XX=P1#point.x

Ovdje se dohvaća samo element pod imenom x i njegova vrijednost stavlja u


varijablu XX.

Pored ovih “glavnih” vrsta podataka Erlang koristi i nekoliko specifičnih tipova
podataka. PID je identifikator procesa (process identifier) i predstavlja oznaku nekog
procesa. Reference je jedinstvena referenca (oznaka) nekog objekta. Također,
postoje i oznake portova i drugi specifični tipovi koje korisnik najčešće ne
upotrebljava.

1.12.2 Varijable

Varijable u Erlangu mogu SAMO jednom poprimiti vrijednost (single assignment) i tu


vrijednost ne mogu mijenjati tijekom izvršenja programa. Imena varijabli moraju
početi s velikim slovom, a osim slova i brojeva, smiju sadržavati još samo podvlaku
(“_”). Ako se varijabla ima dodijeljenu vrijednost, a ne koristi se u funkciji onda se
prilikom prevođenja generira opomenu (warning). Ako ne želimo da se generira
opomena onda naziv varijable treba početi sa znakom “_”.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 37

1.12.3 Podudaranje uzoraka

Uzorak je struktura slična vrstama podataka, s tim da uzorak može sadržavati i


varijable, npr:
{a,A,[3,atom_1,atom_2]}
{{x},y,34}
A
{X,Y}

Podudaranje uzoraka (pattern matching) je postupak uspoređivanja uzorka koji sadrži


varijable s konstantom koja ima istu strukturu. Tada varijable poprimaju vrijednosti
odgovarajućih elemenata konstante.

Primjer:

A=2 (varijabla A poprima vrijednost 2)


{A,B}={12,atom_1} (varijabla A poprima vrijednost 12, a B vrijednost
atom_1)
{X,Y,Z}={2,4} (pogreška jer struktura varijable ne odgovara uzorku)
[U,V]=[{pi,3.14},{e,2.57}]

Operator “|“ se može koristiti za odvajanje dijelova liste i često se koristi u


uspoređivanju uzoraka:

[X|T] (X odvaja prvi element, a T je ostatak liste)

Primjer:

[X|T]=[1,2,3,4,5] (X postaje 1, a T postaje [2,3,4,5])


[A,B|Ostatak]=[prvi,drugi,3,4,5] (A postaje prvi, B drugi, a Ostatak
[3,4,5])

Operator “_” se koristi u slučajevima kada nas ne zanima vrijednost nekog elementa.

Primjer:

[X,_]=[1,2] (X postaje 1)
[A,_|Ostatak]=[prvi,drugi,3,4,5] (A postaje prvi, a Ostatak [3,4,5])
[H,_]=[prvi,drugi,3,4,5] (H postaje prvi)
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 38

1.12.4 Pozivanje funkcija

U Erlangu se funkcije pozivaju jednostavnim navođenjem modula, naziva funkcije i


parametara u sljedećem obliku:

modul:naziv_funkcije(Parametar1, Parametar2,...,ParametarN)

Neke od funkcija (koje se nalaze u modulu erlang) nazivamo BIF funkcijama (Built-in
functions) i uz njih nije potrebno navoditi naziv modula.

Funkcija može imati višestruku definiciju koje se razlikuju po broju i vrsti varijabli koji
prenose. Na taj način se odabirom broja i vrste parametara odabire instanca funkcije
koja će se izvršiti.

Primjer:

funk1(krug,A) -> ....


funk1(kruznica,A) -> ....
funk1(_,A) -> ....

1.12.5 Programi u Erlangu

Programi u Erlangu su organizirani u module u kojima se nalaze skupine srodnih i


logički povezanih funkcija. Svaki modul počinje s ključnom riječi module, iza koje se
navodi naziv modula. Modul se deklarira ovako:

-module(ModuleName)

Moduli se spremaju u datoteke čiji je naziv sljedećeg oblika: ime_modula.erl.


Pozivanje funkcije ovisi o nazivu modula (ModuleName), nazivu funkcije
(FunctionName) te o pojavljivanju naziva funkcije u uvezenoj (import) ili izvezenoj
(export) deklaraciji modula. Sve funkcije koje se mogu pozivati izvan modula (tj. ne
pozivaju same sebe unutar modula) se moraju deklarirati u dijelu export. Navodi se
ključna riječ export iza koje se navodi popis funkcija s brojem parametara koje
koriste.

Da bi se funkcija mogla pozvati iz drugog modula treba biti deklarirana ovako:

-export(FunctionName).

Da bi modul mogao pozvati funkciju iz drugog modula moramo ga uvesti kao što
slijedi:

-import(ModuleName, FunctionName).

Sljedeći primjer se sprema u datoteku prvi.erl.


DEKLARATIVNI KONKURENTNI JEZIK ERLANG 39

-module(prvi).
-export([poruka/0]).
%% Ovo je komentar
poruka() ->
io:format(“Hello world !~n”).

Pojasnit ćemo samo raspoređivanje procesa (scheduling). Algoritam raspoređivanja


treba biti pravedan (fair) tako da će se svi procesi izvesti redom kako su postajali
izvodljivi. Svakom procesu dodjeljuje se vremenski odsječak za izvedbu (oko 500
funkcijskih poziva). Svi se procesi izvode s istim prioritetom; snižavanje kao i promjena
prioriteta su mogući.

1.12.6 Klauzule

Svaka funkcija može imati višestruku definiciju koje se nazivaju klauzule. Klauzule se,
pri izvršenju programa, čitaju slijedno dok se ne pronađe ona koja odgovara pozivu
funkcije.

Primjer:

-module (matematika).
-export([faktorijel/1,povrsina/1]).

faktorijel (0) -> 1;


faktorijel (N) ->
N*faktorijel(N-1).

povrsina({kvadrat,A}) ->
A*A;
povrsina({krug,R}) ->
R*R*3.14;
povrsina({trokut,A,B,C}) ->
S=(A+B+C)/2,
math:sqrt(S*(S-A)*(S-B)*(S-C));
povrsina(Ostalo) ->
{pogreska,Ostalo}.

Klauzule jedne funkcije se odvajaju s znakom “;”, a zadnja klauzula na kraju ima “.”.
Kada se pronađe klauzula koja odgovara pozivu, sve varijable u klauzuli poprimaju
vrijednost (prema vrijednostima koje su proslijeđene funkciji). Nakon izvršenja funkcije
memorija korištena za varijable se oslobađa.

Kod funkcije faktorijel, druga klauzula se izvršava sve dok vrijednost parametra
ne postane 0 i u tom trenutku se poziva prva klauzula koja vraća izračunatu
vrijednost. Funkcija povrsina je razdvojena na više klauzula od kojih je svaka
“zadužena“ za jednu vrstu geometrijskih objekata.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 40

1.12.7 Uvjeti za izvršenje klauzula

U nekim slučajevima nije dovoljno koristiti klauzule za razdvajanje funkcije pa se


koriste i uvjeti pod kojim će se određena klauzula izvršiti.

Primjer faktorijel se može napisati i kao:

factoriel(N) when N>0 ->


N*factoriel(N-1);
factoriel(0) -> 1.

Uvjeti se postavljaju korištenjem ključne riječi when. Pravilno postavljeni uvjeti


omogućuju proizvoljno postavljanje redoslijeda klauzula. Bez uvjeta, krivi redoslijed
naredbi uvjetuje neispravno funkcioniranje programa.

U sljedećem primjeru funkcija će prijeći u “mrtvu” petlji i neće vratiti očekivani


rezultat.

factoriel(N) ->
N*factoriel(N-1);
factoriel(0) -> 1.

Primjeri uvjeta:

is_atom(X) ako je X atom


is_binary(X) ako je X binarna vrsta
is_float(X) ako je X realni broj
is_function(X) ako je X funkcija
is_function(X,N) ako je X funkcija s N argumenata
is_integer(X) ako je X cijeli broj
is_list(X) ako je X lista
is_number(X) ako je X broj
is_pid(X) ako je X identifikator procesa
is_tuple(X) ako je X tuple

length(X)==3 ako je duljina liste X jednaka 3


hd(X)==2 ako je glava liste X jednaka 3
tl(X)==[2] Ako je ostatak liste X jednak listi [2]
size(X)==2 ako je X tuple veličine 2
X>Y+1 ako je X veći od Y+1
X =:= Y ako je X točno jednak Y
1 =:= 1.0 nije točno jednak

1.12.8 Primjeri primjena Erlang funkcija i konstrukcija

Funkcija prosjek računa prosječnu vrijednost elemenata liste.


DEKLARATIVNI KONKURENTNI JEZIK ERLANG 41

prosjek(X) -> sum(X) / len(X).

sum([H|T]) -> H+sum(T);


sum([]) -> 0.

len([_|T]) -> 1+len(T);


len([]) -> 0.

Funkcija double udvostručuje elemente liste, a funkcija member provjerava da li je


neki element član liste.

double([H|T]) -> [2*H|double(T)];


double([]) -> [].

member(H,[H|_]) -> true;


member(H,[_|T]) -> member(H,T);
member(_,[]) -> false.

Funkcija apply izvršava funkciju Fun iz modula Mod s parametrima


Param: apply (math, sin, [3.14]).

Erlang ima case i if naredbe:

case izraz of
uzorak1 ->
naredbe1;
uzorak2 ->
naredbe2;
....
end,

if
uvjet1 ->
naredbe1;
uvjet2 ->
naredbe2
end,

Tablica 5.2 prikazuje operatore uspoređivanja.

Tablica 5.2 – Operatori uspoređivanja


Operator Opis
X>Y X veći od Y
X<Y X manji od Y
X=<Y X manji ili jednak Y
X>=Y X veći ili jednak od Y
X==Y X jednak Y
X/=Y X različit od Y
X=:=Y X je identičan Y (1=:=1.0 daje false)
X=/=Y X nije identičan Y (1=/=1.0 daje true)
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 42

Primjeri:

Klasična „for…next“ petlja izvedena u Erlangu:

-module(lab1).
-export([for/1]).
for(0)->
finish;
for(N)->
io:format("i: ~w i^2 ~w i^3 ~w: ~n", [N,N*N, pot(N,3) ]),
for(N-1).
pot(X,0)->
1;
pot(X,N)->
X*pot(X,N-1).

Klasična „for…next“ petlja uz poštivanje redoslijeda

for(N)->
for2(N,N).
for2(0,_)->
finish;
for2(N,Max)->
I=Max-N+1,
io:format("i: ~w i^2: ~w i^3: ~w ~n", [I,I*I, pot(I,3)
]),
for2(N-1,Max).

1.13 Konkurentno programiranje

1.13.1 Osnovni pojmovi

Proces u Erlangu definiran je kao samostalna obradna jedinka koja postoji


konkurentno s drugim procesima u sustavu pri čemu ne postoji nikakva hijerarhija
procesa osim one koju uvede programer. Konkurentno programiranje u Erlangu
zahtijeva razumijevanje pojmova koji su navedeni u nastavku.

• Proces (process) - osnovni element konkurentnosti. Proces predstavlja virtualni


stroj jer ima vlastitu memoriju i procesorsko vrijeme. Sustav može imati puno
procesa u isto vrijeme.
• Poruka (message) - način komuniciranja između procesa .
• Vremenska kontrola (timeout) - mehanizam za čekanje određenog vremena
nakon koga će se veza proglasiti nemogućom.
• Registrirani procesi - procesi koji imaju “ime” poznato svim procesima.
• Model lijent/poslužitelj (client/server model) - standardni način za
programiranje konkuretnih sustava
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 43

1.13.2 Kreiranje novog procesa

Svaki Erlang proces može kreirati drugi proces. Kao identifikator (oznaku) procesa
koristimo PID (process identifier). Korištenjem PID-a možemo komunicirati s procesom,
a na kraju i narediti njegovo gašenje.

Za kreiranje procesa se koristi naredba spawn sa sljedećom sintaksom:

Pid2=spawn(Mod,Func,Args)

Naredba spawn će stvoriti novi proces, i taj proces će izvršavati funkciju Func iz
modula Mod, s parametrima Args.

Nakon što smo stvorili (kreirali) proces, sljedeći korak je uspostaviti neki oblik
komunikacije između procesa. Kao što je već ranije rečeno, procesi komuniciraju
asinkronom razmjenom poruka. Asinkroni prijenos poruke znači da kada pošiljatelj
pošalje poruku, može je nastaviti s radom bez čekanja na potvrdu od primatelja
poruke (da je primio poruku).

Poruke se razmjenjuju korištenjem operatora “!” i navođenjem PID-a procesa kojem


poruka ide i to na sljedeći način:

PID!Poruka

Na prijemnoj strani poruke se primaju s naredbom receive:

receive
{poruka}->
akcija...
end.

Primjer:
{A_PID, poruka}
A B

B_PID!{self(),poruka} receive
{From,Msg}->
naredbe…
end.

Slika 5.1 – Slanje i primanje poruka

Funkcija self(), korištena u primjeru, vraća vrijednost PID procesa koji je poziva
(vlastiti PID). U primjeru, proces A šalje poruku procesu B (proces u varijabli B_PID
ima PID procesa B). Varijable From i Msg primaju vrijednosti kada poruka dođe u
proces i From postaje PID procesa A, a Msg postaje atom poruka.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 44

Poruke mogu prenositi i podatke u proizvoljnoj strukturi. Na sljedećem primjeru se


prenose znamenke, koje je korisnik telefonskog aparata ukucao, radi provjere
korisničkog broja.

{A_PID, {znamenke,[6,1,2,9,7,5,1]}}
A B

receive
B_PID!{self(),{znamenke,[6,1,2,9,7,5,1]}}
{From,{znamenke,Broj}}->
analiziraj(Broj)
end.

Slika 5.2 – Sadržaj poruke

Sljedeći primjer pokazuje kako se može napisati program (tj. funkcija) koja će na sve
pristigle poruke u proces odgovarati s tom istom porukom (echo).

-module (echo).
-export([go/0,loop/0]).

go()->
Pid2 = spawn (echo, loop, []),
Pid2!{self(), poruka},
receive
{Pid2,Msg}->
io:format(“PID1 ~w~n”,[Msg])
end,
Pid2!stop.

loop() ->
receive
{From,Msg} ->
From!{self(), Msg},
loop()
stop ->
true
end.

1.13.3 Razlike između selektivnog i neselektivnog prijema


poruka

Selektivni prijem poruka za razliku od neselektivnog prima samo određene poruke,


poruke određenog formata ili od određenog pošiljatelja. Osnovna razlike je da kod
selektivnog prijema receive naredbi nemamo varijablu nego atom koji uvjetuje
prijem poruke.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 45

C!poruka1 C

B receive
poruka1->
akcija1…
C!poruka2 poruka2->
akcija2…
end.

Slika 5.3 – Selektivni prijam

Kada poruka dođe u proces, testira se na ponuđene ulaze za poruke i ako ne


odgovara niti jednom neće biti primljena (niti će biti vraćena).

C!poruka1 C

B receive
Poruka->
akcija…
C!poruka2 end.

Slika 5.4 – Neselektivni prijam

Neselektivni prijam poruke prihvaća poruke bez obzira kojeg oblika i sadržaja one
bile. Varijabla Poruka preuzima podatke koji idu s porukom i kasnije se može
koristiti za analizu pristiglog.

Primjer praktične upotrebe procesa i različitih tipova komuniciranja između njih može
se vidjeti na sljedećem primjeru. Funkcija ringing predstavlja stanje u procesu
uspostave telefonske veze između dva korisnika. U trenutku kada strani B zvoni,
strana A može spustiti slušalicu ili se B može javiti.

ringing_a (A,B) ->


receive
{A,on_hook} ->
A!{stop_tone,ring},
B!{terminate},
idle(A);
{B, answered} ->
A!{stop_tone,ring},
switch!{connect,A,B},
conversation_a(A,B)
end.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 46

Identifikatori procesa mogu se slati u porukama kao i drugi podaci. Prebacivanje PID-
a procesa A, preko procesa B, procesu C, pokazano je na primjeru:

A B C

B!{transfer,self()} receive receive


{transfer,A}-> {transfer,A}->
C!{transfer,A} A!echo
end. end.

Slika 5.5 – Prebacivanje identifikatora procesa

U nekim primjenama (npr. kada imamo puno procesa koji komuniciraju s nekoliko
posebnih procesa) moguće je registrirati proces tako da mu se poruke šalju
navođenjem njegovog imena, a ne PID-a. Ovo je naročito korisno u sustavima
klijent/poslužitelj u kojima veći broj procesa koji komuniciraju s jednim (poslužiteljem).
Procesi se mogu registrirati s naredbom register(naziv, PID), gdje je naziv
atom koji predstavlja naziv procesa.

start() ->
Pid=spawn(num_anal, server, []),
register(analyser,Pid).

analyse (Seq) ->


analyser!{self(), {analyse, Seq}},
receive
{analysis_result,R} ->
R
end.

Funkcija start kreira proces i registrira ga kao proces analyser. Nakon toga, svi
procesi mogu slati poruke registriranom procesu analyser bez potrebe da se PID
tog procesa prenosi između procesa i pamti u varijablama.
Osim funkcije register postoji i funkcija unregister koja poništava registraciju.
Parametar te funkcije je atom imena pod kojim je registriran proces. Funkcija
whereis koja ima za parametar atom imena provjerava je li pod navedenim
imenom registriran neki proces. Ako registracija postoji onda se vraća PID tog
procesa, a ako registracije nema onda se vraća undefined. Osim ovih funkcija
postoji i funkcija registered koja vraća listu registriranih imena.

1.13.4 Model klijent/poslužitelj

Sustav s jednim procesom, izvršne namjene, i više korisničkih procesa pokazuje se kao
vrlo čest u raznim primjenama. Takav pristup često nazivamo modelom
klijent/poslužitelj. Poslužitelj je najčešće registrirani proces koji prima poslove na
obradu i vraća rezultate procesima koji obradu traže.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 47

{request,Request}

{reply,Reply}
poslužitelj

Slika 5.6 – Model klijent/poslužitelj

Poslužiteljski dio programa može biti:

server(Data) ->
receive
{From,{request,X}} ->
{R,Data1}=fn(X,Data),
From!{myserver,{reply,R}},
server(Data1)
end.

Klijentski dio programa:

request(Req)->
myserver!{self(),{request,Req}},
receive
{myserver,{reply,Rep}} ->
R
end.

1.13.5 Upotreba vremenske kontrole

Proces u naredbi receive može čekati neograničeno dugo, i na taj način, blokirati
se od daljnjeg rada. Poruka koju proces čeka možda nikada neće doći, jer je možda
proces pošiljatelj uslijed pogreške prekinuo s radom ili je.došlo do kvara na
komunikacijskim kanalima.

Jedini izlaz u takvim situacijama je korištenje vremenske kontrole (timeout) koja


prekida čekanje u receive naredbi nakon određenog broja milisekundi (ms) i
izvršava odgovarajuću naredbu. Korištenje vremenske kontrole je slično običnom
primanju poruke samo što se ovdje koristi klauzula after. Na primjeru to izgleda
ovako:
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 48

receive
poruka1 ->
akcija1;
..........
after
Time ->
akcija2
end.

Primjer pokazuje korištenje vremenske kontrole. Ako nakon Time milisekundi


poruka1 ne dođe u proces (mailbox) izvršava se akcija2.

U nastavku su dati primjeri korištenja vremenske kontrole:

sleep(T)->
receive
after T ->
true
end.

Funkcija sleep pravi pauzu od T milisekundi nakon čega normalno nastavlja s


radom.

suspend() ->
receive
after infinity ->
true
end.

Funkcija suspend trajno “zaglavljuje” funkciju u receive naredbi.

set_alarm(T, What) ->


spawn(timer, set, [self(),T,What]),
receive
Msg ->
......
end.

set(Pid,T,Alarm) ->
receive
after
T->
Pid!Alarm
end.

Funkcija set_alarm postavlja alarm koji će biti poslan procesu nakon T milisekundi s
porukom What.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 49

U sljedećem primjeru pokreću se dva procesa, receiver i sender koji nakon


pokretanja vraćaju svoj PID. Proces sender u razmacima od po 1 sekundu šalje dvije
poruke procesu receiver.

-module(example3).
-export([msgpas/0,receiver/0,sender/0]).

msgpas()->
register(master,self()),
Pid1=spawn(example3,receiver,[]),
Pid2=spawn(example3,sender,[]),
io:format("Procesi otvoreni Pid1: ~w Pid2: ~w ~n",
[Pid1,Pid2]),
loop().

loop()->
receive
{sender,Pid}->
io:format("Sender PID: ~w ~n",[Pid]),
loop();
{receiver,Pid}->
io:format("Receiver PID: ~w ~n",[Pid]),
loop()
after 5000->
io:format("Finish~n")
end.

receiver()->
register(receiver,self()),
master!{receiver,self()},
receiver_loop().
receiver_loop()->
receive
{status,Status}->
io:format("Primjer selektivnog prijema~n"),
io:format("Dosla poruka o statusu ~w ¿
~n~n",[Status]),
receiver_loop();
Neselektivno->
io:format("Primjer neselektivnog prijema~n"),
io:format("Dosla poruka ~w~n~n",[Neselektivno]),
receiver_loop()
end.

sender()->
register(sender,self()),
master!{sender,self()},
sender_loop().
sender_loop()->
receive
after 1000->
receiver!{status,'Sve OK'},
receive
after 1000->
receiver!'nesto drugacije'
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 50

end
end.

Izvršavanje primjera:

> erl
Erlang (BEAM) emulator version 5.6.5 [source] [smp:2] [async-
threads:0] [kernel-poll:false]

Eshell V5.6.5 (abort with ^G)


1> example3:msgpas().
Procesi otvoreni Pid1: <0.22.0> Pid2: <0.23.0>
Receiver PID: <0.22.0>
Sender PID: <0.23.0>
Primjer selektivnog prijema
Dosla poruka o statusu 'Sve OK'

Primjer neselektivnog prijema


Dosla poruka 'nesto drugacije'

Finish
ok
2>

1.13.6 Upravljanje sklopovskim jedinicama

Ulazno/izlazna komunikacija Erlang programa ostvaruje se putem vrata (port) koja


omogućuju međudjelovanje s objektima izvan Erlang sustava kao što su drugi
programski paketi i programi napisani u drugim jezicima. Taj mehanizam može se
upotrijebiti i za upravljanje sklopovskim jedinicama tako da se ostvari komunikacija s
objektima koji to neposredno izvode.

1.14 Raspodijeljeno programiranje i robustnost

Raspodijeljeno programiranje može se opisati kao programiranje sustava čiji elementi


nisu na jednom mjestu (dislocirani su, fizički udaljeni). Motivi za takav način
izvršavanja programa dani su u nastavku:

Brzina

Dijeljenjem aplikacije na više dijelova, koje izvršavaju različiti procesori, dobivamo


skraćenje ukupnog vremena izvršenja. Primjer primjene je program prevoditelj, koji u
isto vrijeme (na različitim mjestima) može prevoditi nezavisne funkcije (dijelove)
programa.

Pouzdanost i obrada pogrešaka

Da bi se povećala pouzdanost sustava, moguće ga je organizirati tako da rad (ili


ispad) nekog od procesora (čvora) ne utječe na rad drugih procesora (čvorova).
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 51

Također, moguće je da se posao s čvora u kvaru rasporedi na ostale čvorove u


sustavu.

Pristup resursima na udaljenim čvorovima

Neki od udaljenih čvorova mogu imati ugrađeni specijalizirani hardver koji će na ovaj
način postati dostupan svima ostalim.

Ugrađena raspodijeljenost u nekim primjenama

Primjena u telekomunikacijama primjer je ugrađene raspodijeljenosti u problemu.

Mogućnost proširenja sustava.

Jednostavno dodavanje novih čvorova omogućuje proširenje sustava i povećanje


procesirajuće snage cijelog sustava.

1.14.1 Referencijska transparentnost

Funkcijski jezici imaju svojstvo referencijske transparentnosti (referential transparency)


što znači da rezultat pozivanja funkcije ne ovisi o tome kada se funkcija poziva već
kako se poziva, odnosno s kojim argumentima. Nema promjene stanja i nema
popratnih pojava, jer se informacija o stanju prenosi eksplicitno argumentima i
rezultatima, a ne promjenom vrijednosti podatka (sadržaja memorije), kao kod
imperativnih jezika. Još je jedna razlika – imperativni jezici tretiraju različito varijable
(čitaj, piši, mijenjaj) i funkcije (pozivaj), a deklarativni ne.

Kod funkcijskih jezika uvijek vrijedi f(x) + f(x) = 2f(x), za razliku od imperativnih kod
kojih ne možemo biti sigurni da se vrijednost f(x) nije promijenila između prvog i
drugog očitavanja tijekom računanja. Zbog referencijske transparentnosti olakšana je
provjera ispravnosti te optimizacija i paralelizacija programa što su važni dobici za
telekomunikacijske primjene.

1.14.2 Ugrađene funkcije koje omogućuju raspodijeljeno


programiranje

Udaljeno računalo (procesor) Erlang tretira kao čvor (node) i to je osnovni element
raspodijeljenog programiranja. Određeni dio ugrađenih funkcija je specijalizirano za
širenje aplikacije (programa) izvan (fizičkih) granica računala (čvora) na kojima se
program pokreće. Stvaranje procesa na udaljenom čvoru vrši se pomoću funkcije
spawn/4:

spawn(Node, Mod, Func, Args)

Slično kao i funkcija spawn/3 kreira proces koji izvršava funkciju Func u modulu Mod
s parametrima Args, ali sada na čvoru Node. Čvor se zadaje u sljedeći način:

ime_čvora@internet_adresa_racunala
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 52

ili npr: master@sef.tel.fer.hr

Čvor dobiva naziv prilikom podizanja Erlanga na računalu i može se zadati u


komandnoj liniji:

erl -name naziv_čvora

Parametar –name omogućuje unos naziva čvora. Ako se parametar izostavi čvor
dobija unaprijed zadano ime (nonode@nohost).

Na jednom UNIX/Windows čvoru može aktivirati više Erlang čvorova korištenjem


različitih imena čvorova (sl. 5.7).

spawn_link(Node, Mod, Func, Args)

Navedena funkcija stvara proces na udaljenom čvoru i otvara link prema tom
procesu. U slučaju prekida rada udaljenom procesa, lokalnom procesu se dojavljuje
razlog prekida rada.

mreža

TCP/IP Driver TCP/IP Driver

Erlang čvor Erlang čvor

UNIX/Windows čvor UNIX/Windows čvor

Slika 5.7 - Veza Erlang i UNIX/Windows čvora i povezivanje preko mreže

U nastavku su navedene funkcije koje omogućuju raspodijeljeno programiranje:

node()
Funkcija node vraća vlastiti naziv čvora.

nodes()
Funkcija nodes() vraća listu svih čvorova trenutno poznatih. Ovo svakako ne znači
da će funkcija vratiti listu svih aktivnih čvorova već samo one s kojima je čvor imao
razmjenu poruka.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 53

node(Item)
Funkcija node(Item) vraća naziv čvora ako je Item ili PID procesa ili referenca ili
port.

monitor_node(Node, Flag)
Funkcija monitor_node pokreće promatranje čvora ako je Flag postavljen na
true. Promatranje čvora podrazumijeva da će čvor koji promatra dobiti poruku
{nodedown, Node} u slučaju da promatrani čvor, zbog nekog razloga prestane s
radom. Ako se Flag postavi na false tada se promatranje prekida.

disconnect_node(Node)
Funkcija prekida vezu sa čvorom Node.

Funkcija monitor_node se može koristiti za provjeru dostupnosti nekog čvora:


monitor_node(Node, true)
receive
{nodedown,Node}->
io:format("Čvor ~w nedostupan! ~n",[Node]);
after 3000 ->
idemo_dalje
end,

Funkcija monitor_node, u slučaju da veza nije ostvarena, ostvaruje vezu ako je to


moguće.

PID koji se dobije kao rezultat djelovanja funkcije spawn/4 potpuno je istovjetan
PID-u procesa na lokalnom računalu. Također je moguće registrirati takve procese.

Prilikom slanja poruka koristi se sljedeća sintaksa:


{Name, Node}! Poruka
Name je registrirani naziv procesa.

Sljedeći primjer pokazuje djelovanje navedenih funkcija (primjer je simulacija rada


Erlanga u tekstualnom režimu):

1> nodes()
[ ]
2>P=spawn(‘master@oluja.tel.fer.hr’,proba,server,[])
<11.3.1>
3>nodes().
[‘master@oluja.tel.fer.hr’]
4>node(P).
‘master@oluja.tel.fer.hr’
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 54

1.14.3 Robustnost

Jezik Erlang omogućuje kontinuirani rad sustava i zamjenu dijelova programa bez
zaustavljanja. Na raspolaganju su tri konstrukta za otkrivanje pogrešaka tijekom
rada, i to:

• nadgledanje izračunavanja izraza kojim se može zaštititi slijedne dijelove


programa od pogrešaka i ne-lokalne povratke iz funkcija. Normalno će
pogreška dovesti do završavanja procesa, a nadgledanjem će se dobiti
informacija o razlogu neispravnosti.
• nadgledanje ponašanja procesa kojim izvedbu nekog procesa prati drugi
proces uporabom veza (link) između procesa i EXIT signala (slika 5.8).
Završavanjem procesa šalje se posebni EXIT signal svim procesima s kojima
je povezan, a koji sadrži razlog završavanja (Reason):
{'EXIT', Exiting_Process_Id, Reason}.
• uočavanje računanja nedefiniranih funkcija kojim se poziv takvoj funkciji
pretvara u poziv posebnom modulu za obradu pogrešaka (error_handler).

Proces Pid1

Pid2 = spawn_link(Mod, Fun, Arg)

Proces Pid1 Proces Pid2

Pid2 = spawn_link(Mod, Fun, Arg)


LINK
receive
{EXIT, From, Reason}->
action()

end.

{EXIT, Pid2, Reason}

Slika 5.8 – Uporaba veze između procesa i slanje signala EXIT

Razloge pogrešaka u programima možemo grupirati na:


• pogreške nastale prilikom usporedbe (matching),
• pogreške koje nastaju pozivanjem ugrađenih funkcija s krivim argumentom,
• pogreške nastale prilikom izračunavanja aritmetičkih funkcija kada je kao
argument poslat ne-broj.

Erlang ima ugrađene neke mehanizme (error handling) koji mogu spriječiti raspad
programa:
• promatranje izvršenja izraza (funkcije),
• promatranje rada drugog procesa,
• sprečavanje izvršenja nedefiniranih funkcija.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 55

Iznimke

Iznimke su mehanizam za promatranje izvršenja izraza. Ukoliko se dogori


nepredviđena situacija baca se iznimka koju se programski može uhvatiti i obraditi.

Iznimke se mogu baciti iz nekoliko razloga:


• interne greške virtualnog stroja (npr. nema funkcije),
• pozvana je funkcija throw(Exception),
• pozvana je funkcija exit(Exception),
• pozvana je funkcija erlang:error(Exception).

Iznimke se mogu programski obraditi. To se može napraviti na dva načina:


• ograditi poziv funkcije koja baca iznimku izrazom try…catch,
• označiti poziv funkcije izrazom catch.

Funkciju exit(Reason) ćemo koristiti samo kada se želi ugasiti trenutni proces. Ova
funkcija baca iznimku koja se razašilje svim povezanim procesima (linked) ako se ne
uhvati.

Funkciju throw(Reason) ćemo koristiti kada korisnik funkcije može, ali ne mora
napraviti obradu iznimke. Ako korisnik smatra potrebnim onda će poziv funkcije
ograditi try…catch izrazom i obraditi ju, a ako ne onda se ona ignorira.

Funkcijeu erlang:error(Reason) ćemo korisiti za kritične greške (crashing errors).


Takve greške su ozbiljne i nisu namijenjene korisnicima da ih obrađuju.

Općeniti oblik try…catch izraza je sljedeći:

try Func of
Pattern1 [when Guard1] -> Exps1;
Pattern2 [when Guard2] -> Exps2;
...
catch
ExceptionType1: ExPattern1 [when ExGuard1] -> ExExps1;
...
after
AfterExps
end

Prvo se pozove funkcija Func. Ako nije bačena iznimka onda se izvršava dio s
uzorcima (Patteren1, …). Ako je bačena iznimka onda se izvršava dio iza catch
tj. ispituje se vrsta iznimke i podudaraju se uzorci (ExceptionType1:
ExPattern1). Vrsta iznimke mogu biti atomi: throw, exit ili error. Ako vrsta
iznimke nije navedena pretpostavlja se da je throw. Interne greške irtualnog stroja
su uvijek error. U dijelu after se nalaze izrazi koji se uvijek izvršavaju bez obzira
da li se dogovila iznimka ili ne. Taj dio se izvršava odmah nakon poziva funkcije, a
tek iza toga se probaju podudaradi normalni uzorci ili uzorci greški.

Funkcija vraća n-torku {‘EXIT’,Reason} gdje je Reason opis pogreške.


Funkcija throw se koristi u kombinaciji s catch i koristi se za izravno slanje poruke u
catch. Sljedeći primjer ilustrira bacanje iznimki.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 56

test(1,X)->
{ok,X};
test(2,X)->
throw({myerror,X});
test(3,X)->
tuple_to_list(X);
test(4,X)->
exit({myExit,X});
test(5,X)->
{'EXIT', X};
test(6,X)->
erlang:error(X).

Slijedi ispis izvršavanja:

2> prvi:test(1,x).
{ok,x}

Ovdje se sve izvršilo normalno.

3> prvi:test(2,x).
** exception throw: {myerror,x}
in function prvi:test/2

Ovdje je bačena iznimka throw, a razlog je {myerror,x}.

4> prvi:test(3,x).
** exception error: bad argument
in function tuple_to_list/1
called as tuple_to_list(x)
in call from prvi:test/2

Ovdje je bačena iznimka error jer se očekivala n-torka, a poslan je atom funkciji
tuple_to_list.

5> prvi:test(4,x).
** exception exit: {myExit,x}
in function prvi:test/2

Ovdje je bačena iznimka exit, a razlog je {myExit,x}.

6> prvi:test(5,x).
{'EXIT',x}

Ovdje je sve normalno izvršeno.

7> prvi:test(6,x).
** exception error: x
in function prvi:test/2
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 57

Ovdje je bačena iznimka error, a razlog je x.

8> prvi:test(7,x).
** exception error: no function clause matching prvi:test(7,x)

Ovdje je bačena iznimka error, a razlog je što nema funkcije koja prima prvi
parametar 7.

Sljedeći primjer pokazuje korištenje mehanizma hvatanja iznimaki.

test_catch(N,X)->
try test(N,X) of
Value -> {N, normal, Value}
catch
throw:Y -> {N, in_catch, was_throw, Y};
exit:Y -> {N, in_catch, was_exit, Y};
error:Y -> {N, in_catch, was_error, Y}
end.

run()->
[test_catch(N,x) || N <- [1,2,3,4,5,6,7]].

Slijedi izvršavanje.

1> prvi:run().
[{1,normal,{ok,x}},
{2,in_catch,was_throw,{myerror,x}},
{3,in_catch,was_error,badarg},
{4,in_catch,was_exit,{myExit,x}},
{5,normal,{'EXIT',x}},
{6,in_catch,was_error,x},
{7,in_catch,was_error,function_clause}]

Ponekad možemo kod za hvatanje iznimki skratiti. Ovaj kod

try F of
Val -> Val
catch

end

i sljedeći kod rade istu stvar.

try F
catch

end
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 58

Sljedeći kod služi za hvatanje svih iznimki.

try Expr
catch
_:_ -> %% kod za obradu svih iznimki
end

Kada se uhvati iznimka dobro je znati gdje se iznimka dogodila tj. koje funkcije su
bile pozvane. Da bismo dobili taj podatak koristimo funkciju erlang:get_stacktrace()
koja vraća polje n-torki gdje svaka predstavlja jedan poziv metode.

6> erlang:get_stacktrace().
[{prvi,test,[7,x]},
{prvi,test_catch,2},
{prvi,'-run/0-lc$^0/1-0-',1},
{prvi,'-run/0-lc$^0/1-0-',1},
{erl_eval,do_apply,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]

Ovdje vidimo da je iznimka bačena u modulu prvi u funkciji test, a ona je pozvana
s dva parametra 7 i x. Tu funkciju je pozvala funkcija test_catch iz istog modula,
a ona je imala jedan parametar i to je 2, itd.

1.14.4 Gašenje procesa

Uobičajeni način gašenja procesa u Erlangu je slanjem poruke PID!{kraj}.


Naravno, proces mora imati odgovarajuću klauzulu u naredbi receive koja će
pokrenuti funkciju exit i prekinuti izvršenje funkcije.

1.14.5 Povezani procesi

Mehanizmi koji omogućuju jednom procesu da prati rad drugog (linked processes) su
link i EXIT signal. Funkcija spawn_link kreira proces i stvara link prema njemu.
Kada promatrani proces prestane s radom (normalno ili uslijed neke pogreške)
“promatraču” se šalje EXIT signal sljedećeg formata:

{‘EXIT’,Exiting_process_id,Reason}.

Osnovni programski konstrukti koji omogućavaju raspodijeljeno programiranje su


kreiranje novog procesa na udaljenom čvoru (node_link), stvaranje veze prema
udaljenom procesu (link) te izmjena poruka s kreiranim procesom. Primjer s dva
povezana procesa koji se nalaze na različitim čvorovima prikazan je na slici 5.9.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 59

Proces Pid1
NodeA
Pid2 = spawn_link(NodeB, Mod, Fun, Arg)

{Pid2, NodeB}!Message

Message LINK
signal EXIT

NodeB receive
Message->
akcije…
end.

Proces Pid2

Slika 5.9 – Raspodijeljeno procesiranje

Dodatni programski konstrukti koji omogućavaju raspodijeljeno programiranje su


sljedeći:

• pridruživanje, oživljavanje novog čvora (alive),


• link prema čvoru (link_node(node)),
• lista trenutno aktivnih čvorova (nodes()),
• raspodjela opterećenja:
o statička (unaprijed),
o dinamička (na temelju promatranja funkcijom,
statistics(run_queue)).

Slika 5.10 prikazuje kreiranje povezanih procesa i veze prema čvoru s mogućnošću
mjerenja opterećenja.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 60

Proces Pid1
NodeA
nodes()
node_link(NodeB)

LINK

TCP/IP
RPC signal EXIT

NodeB alive(NodeB, Port, Setup)

statistics(run_queue)

Proces Pid2

Slika 5.10 – Stvaranje povezanih procesa

Sljedeći primjer ilustrira djelovanje EXIT signala na skupinu vezanih procesa:

-module(normal).
-export([start/1,p1/1,test/1]).

start(N) ->
register(start,spawn_link(normal,p1,[N-1])).

p1(0)->
top1();
p1(N)->
top(spawn_link(normal,p1,[N-1]),N).

top(Next,N)->
receive
X ->
Next ! X,
io:format(“Proces ~w primio ~w ~n”,[N,X]),
top(Next,N)
end.

top1()->
receive
stop ->
io:format(“Posljednji proces zavrsava! ~n”,[]),
exit(finished);
X->
io:format(“Posljednji proces primio ~w ~n”,
[X]),
top1()
end.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 61

test(Mess)->
start!Mess.

Primjer kreira N povezanih procesa, za N=3:

normal:start(3).

kreiraju se tri procesa, od kojih je prvi registriran imenom start (sl. 5.11).

registrirani proces zadnji proces


start

Slika 5.11 – Kreiranje povezanih procesa

Nakon pokretanja funkcije test s argumentom 321:

normal:test(321).

šalje se poruka 321 procesima slijedno:

Proces 2 primio 321


Proces 1 primio 321
Posljednji proces primio 321

Nakon slanja poruke stop:

normal:test(stop).
Proces 2 primio stop
Proces 1 primio stop
Posljednji proces zavrsava
stop

Procesi završavaju i ukidaju se također slijedno, jedan za drugim, slanjem signala


EXIT (sl. 5.12).
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 62

signal EXIT

registrirani proces zadnji proces


start

signal EXIT

registrirani proces proces 2


start završava

proces start
završava

Slika 5.12 – Slanje exit signala i završavanje procesa

Nakon završetka i ukidanja procesa, slanje nove poruke nije moguće prije ponovnog
kreiranja procesa:

normal:test(654).

Nakon pozivanja funkcije test, Erlang vraća pogrešku jer proces start ne postoji.

1.15 Korištenje Erlangove ljuske

Nakon instaliranja Erlanga ljuska se pokreće naredbom erl. Kao posljedica


pokretanja Erlanga (bez ikakvih parametara) dobiva se sljedeća poruka:

andromeda-3:~ mario$ erl


Erlang (BEAM) emulator version 5.6.5 [source] [smp:2] [async-
threads:0] [kernel-poll:false]

Eshell V5.6.5 (abort with ^G)


1>

Za pisanje programa može se koristiti bilo koji uređivač (editor) teksta (notepad,vi,
joe, …) . U nastavku slijedi prvi program koji je smejšten u datoteku prvi.erl.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 63

-module(prvi).
-export([poruka/0]).

poruka()->
io:format("Ovo je prvi program u Erlangu!").

Učitavanje i prevođenje programa u Erlangu obavlja naredba za prevođenje sa


sintaksom:

c:c(naziv_modula).

U našem slučaju korititi ćemo:

1> c(prvi).
{ok,prvi}

Ispisana poruka {ok,prvi} označava da je modul preveden i stvorena je datoteka


s kodom: prvi.beam. U slučaju pogreške u prevođenju, korisnik će biti obaviješten o
vrsti i mjestu pogreške.

Izvršenje prevedenih funkcija počinje s:

naziv_modula:naziv_funkcije(Popis_parametara).

U našem prvom primjeru: prvi:poruka().

2> prvi:poruka().
Ovo je prvi program u Erlangu!ok

Ako se nešto zablokira i ne možemo ništa upisati u ljusci onda je potrebno pritisnuti
CTRL+C i odabrati opciju abort za izlazak iz virtiualnog stroja (vidi sljedeći isječak
izvođenja)

3>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
a
andromeda-3:erlang mario$

1.15.1 Erlang iz naredbenog retka

Prevođenje iz naredbenog retka vrši se naredbom:

erlc prvi.erl
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 64

Pokretanje programa iz naredbenog retka vrši se naredbom:

erl -noshell -s prvi poruka -s init stop

Opcija –noshell označva da se neće pokretati ljuska. Opcije –s Mod Fun


pokreće funkciju iz zadanog modula. Funkcija stop iz modula init zaustavlja
izvođenje Erlangovog virtualnog stroja.

1.15.2 Korisne naredbe Erlangove ljuske

f().
Kaže ljusci da zaboravi sve varijable koje smo dodijelili. Ova funkcija je korisna kod
isprobavanja.

c(Mod).
Ovu funkciju smo već spomenuli prije. Ona prevodi izvorni kod modula u Erlangu u
izvršni kod tj. iz datoteke npr. x.erl stvara x.beam.

pwd().
Ispisuje trenutni direktorij u kojem se nalazi ljuska.

cd("c:/work").
Premješta ljusku u direktorij work na C disku. Kao što vidite umjesto obrnute kose crte
koristi se obična kosa crta za odvajanje direktorija.

q().
Završava izvođenje ljuske i gasi virtualni stroj.

init:get_argument(home).
Vraća direktorij u kojem je pokrenut virtualni stroj.

code:get_path().
Vraća listu direktorija u kojima virtualni stroj traži prevedene module.

code:add_patha(Dir).
Dodaje direktorij na početak liste direktorija koji se pretražuju kod učitavanja
prevedenog modula.

code:add_pathz(Dir).
Dodaje direktorij na kraj liste direktorija koji se pretražuju kod učitavanja
prevedenog modula.
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 65

1.16 Korištenje Erlanga iz Eclipsa

Za korištenje Erlanga iz razvojnog okruženje Eclipse potrebno je instalirati Erlang na


računalu te instalirati Eclipsov plugin erlIDE (http://erlide.sourceforge.net/). Detaljne
upute za podešavanje potražite na stranicama plugina.

Nakon toga možemo stvoriti novi Erlang Project i u njega dodavati module. Primjer
stvorenog modula slijedi.

%% Author: mario
%% Created: 2009.02.19
%% Description: TODO: Add description to prvi
-module(prvi).

%%
%% Include files
%%

%%
%% Exported Functions
%%
-export([]).

%%
%% API Functions
%%

%%
%% Local Functions
%%

Nakon toga možemo dodati funkciju iz prethodnog primjera (Sl. 5.13).


DEKLARATIVNI KONKURENTNI JEZIK ERLANG 66

Slika 5.13 – Razvojna okolina u Eclipsu

Pokretanje se vrši preko konfiguracija za pokretanje gdje se mogu podesiti svi


relevantni parametri npr. modul i funkcija koja će se pokretati (Sl. 4.14).

Slika 5.14 – Konfiguracija za pokretanje programa


Konkurentnost u objektno orijentiranim jezicima 67

6. Konkurentnost u objektno orijentiranim jezicima


Upotreba objektno orijentiranih jezika u izgradnji programskih sustava koji koriste
grafičko sučelje, mrežu, velike datoteke i sl. uzrokovalo je ugradnju konkurentnih
mehanizama. Konkurnentost u programskim sustavima omogućuje:
• poboljšanje performansi u složenim aplikacijama,
• bolje iskorištavanje višeprocesorskih sustava,
• povećanje aplikacijske propusnosti te
• povećanje interaktivnosti u aplikacijama (posebno u grafičkom sučelju).
Najčešća područja uporabe konkurentnosti u aplikacijama su:
• mrežno programiranje,
• složeni asinkroni sustavi,
• aplikacije s grafičkim sučeljem,
• aplikacije s intenzivnim U/I operacijama i
• distribuirane aplikacije.
Konkurentnost u većini operacijskih sustava zasniva se na konceptu procesa (vidi
poglavlje 3.1.3) i niti (vidi poglavlje 3.1.4) (slika6.1). Računalo ima jednu zajedničku
memoriju kojoj pristupaju procesori. Svaki procesor u jednom trenutku može izvoditi
jedan proces. Proces se može statojati od niti koje se izvode unutar procesa. Ovisno o
načinu raspoređivanja procesorskih vremena svaki procesor određeno vrijeme
izvršava program jednog procesa te nakon toga može izvršavati program drugog
procesa, itd.

zajednička memorija
Legenda:

P P P ... P procesor P

proces

nit

Slika 6.1 - Koncept konkurentnosti u operacijskom sustavu

Proces kao apstrakcija fizikalnog procesora održava skup resursa potrebnih za


izvođenje programa. Svakom procesu se prilikom stvaranja dodjele resursi. Ukoliko su
mu potrebni dodatni resursi zatraži ih od operacijskog sustava npr. dodatna
memorija. Procesi, u pravilu, međusobno ne dijele resurse osim u slučaju mehanizma
dijeljene memorije (shared data) kojeg treba podržavati operacijski sustav.
Nit ili dretva (thread) za razliku od procesa nema vlastite podatke, već ih dijeli s
drugim nitima unutar istog procesa. Nit izvodi naredbe u slijedu te ima svoje stanje
obrade.
Konkurentnost se u programski jezik može ugraditi na sljedeće načine:
Konkurentnost u objektno orijentiranim jezicima 68

• u jezik,
• u biblioteke ili
• koristiti ugrađene mehanizme operacijskog sustava.
Svaki objektno orijentirani jezik s ugrađenom konkurentnošću ima definiranu najmanju
jedinicu ekskluzivnog pristupa objektu. Najmanja jedinica ekskluzivnog pristupa
objektu može biti:
• naredba,
• blok naredbi ili
• objekt.

1.17 Virtualni procesori


Virtualni procesor je kontrolna nit koja može izvršavati slijedne operacije jednog ili
više objekata. Dakle, virtualnom procesoru se dodjeljuju objekti. Svaki objekt ima
samo jedan virtualni procesor kojem je dodijeljen i samo taj virtualni procesor može
izvršavati operacije u metodama tog objekta.
Kada jedan objekt pozove metodu drugog objekta onda se prvo provjerava jesu li
oba objekta dodijeljena istom virtualnom procesoru. Ukoliko jesu, izvršavanje se
nastavlja. Ukoliko nisu, postoje dva scenarija rješavanja ovog problema:
• izvođenje objekta koji poziva metodu se zaustavlja dok virtualni procesor
drugog objekta ne izvrši pozvanu metodu;
• izvođenje objekta koji poziva metodu se nastavlja izvršavati do dijela koda
koji koristi rezultat pozvane metode. Tada se provjerava je li drugi virtualni
procesor izvršio pozvanu metodu i izračunao rezultat. Ako je, onda se
izvršavanje nastavlja, a ako nije onda se izvođenje zaustavlja.
U ovom pristupu je konkurentnost ugrađena u jezik, a najmanja jedinica
ekskluzivnog pristupa objektu je metoda. Komunikacija između konkurentnih
aktivnosti je sinkrona, a dijeljenje resursa napravljeno komunikacijom između
objekata s različitim dodijeljenim procesorima.

1.18 Kompatibilne aktivnosti


Kompatibilne aktivnosti su one aktivnosti koje se mogu izvoditi istodobno, a
nekompatibilne koje se ne mogu. Svaka klasa može biti konkurentna, polukonkurentna
ili slijedna. U konkurentnoj klasi samo jedna nit može izvoditi kompatibilne metode u
jednom trenutku. Kod polukonkurentnih klasa definiraju se metode i njihova
kompatibilnost s drugim metodama u klasi, a kod slijedne klase je kompatibilnost
ignorirana tako da svaka nit može izvoditi bilo koju metodu u bilo kojem trenutku.
Postupak izvođenja je sljedeći, kada neka nit pozove metodu koja nije kompatibilna s
trenutno pozvanim metodama onda se ta nit stavlja na čekanje dok nekompatibilna
aktivnost ne završi. Nakon toga sve niti koje čekaju postaju kandidati za izvođenje te
se jedna od njih odabere i krene izvoditi (obično ona koja najduže čeka).
Konkurentne aktivnosti se u ovom slučaju mogu generirati pomoću autonomnih
objekata ili asinkronih poziva metoda (izvođenje se nastavlja dok se ne koristi
rezultat asinkronog poziva metode). Ovdje je konkurentnost ugrađena u jezik.
Najmanja jedinica ekskluzivnog pristupa objektu je metoda, a dijeljenje resursa je
napravljeno preko poziva kompatibilnih metoda.
Konkurentnost u objektno orijentiranim jezicima 69

1.19 Kompleksna konkurentnost


Postoji klasa Proces koja predstavlja aktivni objekt. Svaki aktivni objekt ima svoj
kontekst u kojem se izvodi. Svaki objekt je dio samo jednog konteksta. Aktivni procesi
komuniciraju ovim redoslijedom:
• slanje zahtjeva od procesa p1 za poziv metode u drugom kontekstu (proces
p2),
• proces p2 se privremeno prekida (interrupted),
• vrši se pregovaranje među procesima i stavljanje poziva u listu poziva koji će
se izvršiti u budućnosti i
• nastavljanje izvođenja prekinute aktivnosti procesa p1 i p2.
Proces p1 se izvodi dok mu ne zatreba rezultat poziva metode. Kada treba rezultat
može se dogoditi jedan od sljedeća dva slučaja:
• metoda još nije izvršena – onda se proces p1 blokira dok se metoda ne izvrši
i ne dobije rezultat ili
• metoda je izvršena i imamo rezultat – onda se izvođenje procesa p1
normalno nastavlja.
Pri kompleksnoj konkurentnosti je konkurentnost ugrađena u jezik, a najmanja jedinica
ekskluzivnog pristupa objektu je metoda. Komunikacija je sinkrona kod prijenosa
parametara poziva metode, a asinkrona kod prijenosa rezultata. Dijeljenje resursa je
napravljeno preko komunikacije između objekata iz različitih konteksta.
Konkurentnost u Javi 70

7. Konkurentnost u Javi
Niti ili dretve (threads) su osnovne jedinice koje se konkurentno izvode u Java izvršnom
sustavu. U nekim tekstovima se za niti koristi i pojam lagani proces (lightweight
process). U Javi postoji klasa Thread koja podržava bogatu kolekciju metoda za
upravljanje nitima.
Prva aplikacija u Javi koja je koristila višenitnost je preglednik HotJava. Unutar njega
možemo konkurentno pomicati stranicu, "skidati" sliku, applet, animaciju ili zvuk,
printati stranicu u pozadini dok učitavamo novu stranicu, gledamo applete koji nešto
rade, ...

1.20 Stvaranje i pokretanje niti


Da bi se neki objekt mogao ponašati kao nit mora:
• biti naslijediti klasu Thread ili
• mora implementirati Runnable sučelje.
Osim toga potrebno je napisati metodu run. Ako klasa mora nasljeđivati neku drugu
klasu onda se obavezno koristi sučelje Runnable da bi se ponašala kao nit. Moramo
naglasiti da nit nije objekt instanciran is klase Thread. Klasa Thread samo služi
kako omotač pomoću kojeg možemo upravljati s nekom niti. Sljedeći primjer pokazuje
kako se stvara i pokreće nit pomoću nasljeđivanja.
public class FirstThreadExtends
extends Thread {

public void run() {


for (int i = 0; i < 5; i++) {
System.out.println("broj " + i);
}
System.out.println("Gotovo brojenje");
}

public static void main(String[] args) {


new FirstThreadExtends().start();
new FirstThreadExtends().start();
}
}
U ovom primjeru imamo klasu FirstThreadExtends koja nasljeđuje klasu Thread
i implementira metodu run. Sve aktivnosti koje se izvode unutar niti definiraju se
unutar tijela niti, a nalaze se unutar metode run. U metodi main stvaramo dva
objekta iz klase FirstThreadExtends i kada se nad njima pozove metoda start
onda se stvara nit koja pozove metodu run. Funkciju koju nit mora izvesti napisana je
u metodi run.
U metodi run se izvodi petlja i u svakoj iteraciji se ispisuje jedan redak. Na kraju se
ispisuje da je brojenje završilo. Mogući ispis je sljedeći:
broj 0
broj 1
broj 0
broj 2
broj 1
broj 3
broj 2
Konkurentnost u Javi 71

broj 4
broj 3
Gotovo brojenje
broj 4
Gotovo brojenje
Sljedeći primjer pokazuje kako stvara i pokreće nit pomoću implementiranja sučelja.
public class FirstThreadImplements
implements Runnable {

public void run() {


for (int i = 0; i < 5; i++) {
System.out.println("broj " + i);
}
System.out.println("Gotovo brojenje");
}

public static void main(String[] args) {


FirstThreadImplements t1, t2;
t1 = new FirstThreadImplements();
new Thread(t1).start();
t2 = new FirstThreadImplements();
new Thread(t2).start();
}
}
U ovom primjeru imamo klasu FirstThreadImplements koja implementira sučelje
Runnable i ima metodu run koja je ista kao i u prošlom primjeru. Razlika je još u
metodi main u kojoj se pokretanje niti vrši tako da se stvori objekt iz klase
FirstThreadImplements, zatim se stvori objekt iz klase Thread kojem se u
konstruktoru proslijedi objekt klase FirstThreadImplements. Nakon toga se
pozove metoda start u objektu Thread.

1.21 Imenovanje i privremeno zaustavljanje niti


Svaka nit ima ime koje se može ispisati. Ako nismo eksplicitno naveli ime niti onda nit
dobije ime. Nit možemo imenovati na dva načina: postavljanjem imena pomoću
konstruktora (Thread(String name)) ili postavljanjem pomoću metode setName.
Dohvaćanje imena niti je moguće pozivom metode getName.
Nit je moguće privremeno zaustaviti na određeno vrijeme. Da bi se to postiglo koristi
se metoda sleep. Ova metoda ima dvije verzije:
• sleep(long millis) throws InterruptedException – zaustavlja
nit na određen broj milisekundi i
• sleep(long millis, int nanos) throws
InterruptedException – zaustavlja nit na određen broj milisekundi +
nanosekundi.
U periodu dok je nit zaustavljena druga nit može pozvati metodu interrupt koja
će prekinuti izvođenje niti koja je u metodi sleep tako da metoda sleep baci
iznimku InterruptedException.

Sljedeći primjer sastoji se od dvije klase ThreadExample koja nasljeđuje klasu


Thread iz paketajava.lang.
public class ThreadExample
Konkurentnost u Javi 72

extends Thread {
public ThreadExample(String name) {
super(name);
}

public void run() {


for (int i = 0; i < 5; i++) {
System.out.println("Nit " + getName() + " : i = " + i);
try {
sleep( (int) (Math.random() * 1000));
}
catch (InterruptedException ie) {
}
}
System.out.println("Gotova nit " + getName());
}

public static void main(String[] args) {


new ThreadExample("Dinamo").start();
new ThreadExample("Hajduk").start();
}
}
Metoda run sadrži petlju koja se izvodi 5 puta. U svakoj iteraciji se ispisuje ime niti i
kolika je vrijednost varijable. Nakon toga nit poziva metodu sleep koja zaustavlja
nit za određen broj milisekundi koji se dobije slučajno pomoću metode random u klasi
Math. Kada se izvede svih 5 iteracija onda se ispiše da je nit gotova s izvođenjem. U
metodi main se stvaraju i pokreću dvije niti pod imenima "Dinamo" i "Hajduk".
Mogući ispis je sljedeći:
Nit Dinamo : i = 0
Nit Hajduk : i = 0
Nit Hajduk : i = 1
Nit Hajduk : i = 2
Nit Dinamo : i = 1
Nit Hajduk : i = 3
Nit Hajduk : i = 4
Nit Dinamo : i = 2
Nit Dinamo : i = 3
Gotova nit Hajduk
Nit Dinamo : i = 4
Gotova nit Dinamo

1.22 Stanja niti


Slika 7.1 prikazuje dijagram različitih stanja niti za vrijeme svog životnog ciklusa.
Konkurentnost u Javi 73

alive

- sleep()
- suspend() / resume()
new - wait() / notify()
yield()
- kritični odsječak

start()
New Not
Runnable
thread runnable

[završila metoda run()] stop()

dead

Slika 7.1 - Stanja u životu jedne niti

1.22.1 Stanje New Thread


Instanciranje niti:
Thread myThread = new Thread();
postavlja nit u stanje New Thread. U ovom stanju je nit „prazan“ objekt Thread
kojemu nisu pridruženi nikakvi sistemski resursi. Iz ovog stanja nit se može pokrenuti
pozivanjem metode start ili se može „ubiti“ pozivanjem metode stop. Ako se
slučajno pozove neka druga metoda osim ove dvije onda će doći do iznimke
IllegalThreadStateException.

1.22.2 Stanje Runnable


Ako se u stanju New Thread pozove metoda start onda se niti dodjeljuju resursi
potrebni za izvođenje i poziva se run metoda. Pokretanjem metode run nit prelazi u
stanje Runnable. U tom stanju se nit stvarno izvodi. Ako računalo ima samo jedan
procesor onda se procesorsko vrijeme raspoređuje po aktivnim nitima. Prilikom
raspodjele uzimaju se u obzir prioriteti niti. Ako je računalo višeprocesorsko ili ima
višejezgreni procesor onda se raspoređivanje niti vrši po vremenu i po procesorima.

1.22.3 Stanje Not Runnable


Nit može doći u ovo stanje na nekoliko načina:
• pozvana je metoda sleep,
• pozvana je metoda suspend (ovo se više ne koristi i treba izbjegavati),
• nit je pozvala svoju metodu wait,
• nit je blokirana od strane U/I jedinice.
U prošlom primjeru kada je pozvana metoda sleep, nit je zaustavila izvođenje na
određen broj milisekundi (nit je u stanju Not Runnable). Nakon tog vremena nit se
ponovo prebacuje u stanje Runnable i pokreće se ako je procesor slobodan.
Za svaki od načina ulaska u stanje Not Runnable postoji određen postupak vraćanja u
stanje Runnable. U nastavku slijede postupci:
• ako je nit u metodi sleep onda treba proći naveden broj milisekundi da bi se
nit vratila u stanje Runnable;
Konkurentnost u Javi 74

• ako je nit u metodi suspend onda neka druga nit mora pozvati metodu
resume;
• ako nit čeka na uvjetnu varijablu onda objekt koji ju posjeduje mora pozvati
metode notify ili notifyAll i
• ako je nit blokirana od strane U/I jedinice onda U/I jedinica mora završiti
svoju obradu i deblokirati nit.

1.22.4 Stanje Dead


Nit može "umrijeti" (doći u stanje Dead) na dva načina:
• pozivanje metode stop koja baci iznimku ThreadDeath niti koja ju ubije ili
• prirodnim načinom tj. završavanjem metode run.
Prilikom pozivanja metode stop može se desiti da se usred nekog važnog posla
naprasno prekine izvođenje niti pa se to ne preporuča, već se u normalnim
situacijama postavlja zastavica, a nit ju treba provjeravati u petlji. Ako je zastavica
postavljena onda nit treba izaći iz petlje završiti izvođenje metode run. Na taj način
nit normalno završava svoje izvođenje. Metoda stop bi se trebala koristiti samo u
krajnjim slučajevima kada se grubo prekida izvođenje. Sljedeći primjer pokazuje
normalno prekidanje niti.
public class NormalExitExample
extends Thread
{
private boolean finishFlag = false;

public void setFinishFlag()


{
finishFlag = true;
}

public void run()


{
for ( int i = 0; i < 5 && !finishFlag; i++ )
{
System.out.println("Nit " + getName() + " : i = " + i );
try
{
sleep( 2000 );
}
catch ( InterruptedException ie )
{
}
}
if ( finishFlag )
System.out.println( "Nit prekinuta prije kraja" );
else
System.out.println( "Gotova nit " + getName() );
}

public static void main( String[] args )


throws InterruptedException
{
NormalExitExample thread = new NormalExitExample();
thread.start();
Konkurentnost u Javi 75

sleep( 3000 );
thread.setFinishFlag();
}
}
Glavna nit nakon 3 sekunde pozove metodu setFinishFlag koja postavlja atribut
finishFlag. Taj atribut se u niti provjerava u svakoj iteraciji petlje. Ako je
postavljen izlazi se iz petlje i nit završava.

1.22.5 Iznimka IllegelThreadStateException


Izvršni sustav baca ovu iznimku kada se pozove metoda niti koju se me smije pozivati
u stanju u kojem se nit nalazi. Npr. iznimka se baca kada se pozove wait u niti koja
nije u stanju Runnable.

1.22.6 Metoda isAlive


Pomoću ove metode možemo saznati u kojem se stanju nit nalazi. Ona vraća:
• true – ako je nit u stanjima Runnable ili Not Runnable,
• false – ako je nit u stanjima New Thread ili Dead.
• Nikako se ne mogu razlikovati stanja Runnable od Not Runnable ili New Thread
od Dead.
U Javi se nikako ne mogu razlikovati stanja Runnable od Not Runnable ili New Thread
od Dead. Slijedi primjer izvođenja metode isAlive.
public class IsAliveExample {
public static void main(String[] args) {
ThreadExample te = new ThreadExample("Brojac");
te.start();

while (te.isAlive()) {
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
}
}

System.out.println("Main je gotov");
}
}
U ovom primjeru se pokreće jedna nit (klasa ThreadExample) i u petlji se
provjerava da li ta nit i dalje radi ili je gotova s izvođenjem. Ako radi (metoda
isAlive vraća true) trenutna nit se privremeno zaustavlja na 10 milisekundi i
nakon toga se ponovno vrši provjera. Kada je nit ThreadExample gotova onda će
metoda isAlive vratiti false i petlja će završiti.

1.22.7 Metoda join


Metoda join zaustavlja trenutno izvođenje niti do trenutka kada nit nad kojom je
pozvana metoda join završi. Sljedeći primjer pokazuje korištenje metode join.
public class JoinExample {
public static void main(String[] args) {
Konkurentnost u Javi 76

ThreadExample te = new ThreadExample("Brojac");


te.start();

try {
te.join();
}
catch (InterruptedException e) {
}

System.out.println("Main je gotov");
}
}
Ispis je sljedeći:
Nit Brojac : i = 0
Nit Brojac : i = 1
Nit Brojac : i = 2
Nit Brojac : i = 3
Nit Brojac : i = 4
Gotova nit Brojac
Main je gotov
U metodi main je stvorena i pokrenuta nit iz klase ThreadExample. Nakon toga je
pozvana metoda join koja je zaustavila nit koja izvodi metodu main. Kada je nit
koja iz klase ThreadExample završila s izvođenjem onda je nastavljeno izvođenje
niti koja je pozvala metodu join.

1.23 Deamon niti


Svaka nit može postati deamon nit. Deamon nit je nit koja poslužuje druge niti unutar
istog procesa, tj. pruža im usluge. U deamon niti metoda run je obično realizirana
pomoću beskonačne petlje u kojoj se realiziraju usluge koje ta nit pruža. Kada je
jedina nit u procesu deamon nit, onda ju izvršni sustav ubije. To je logično jer deamon
nit nema smisla ako nema niti kojima bi pružala svoje usluge.
Da bi neka nit postala deamon nit potrebno je pozvati setDeamon metodu s
argumentom true. Da bi utvrdili je li neka nit deamon nit pozovemo metodu
isDeamon koja vraća true ili false kao rezultat.

1.24 Grupa niti


U Javi je podržano grupiranje niti da bi se lakše rukovalo s više niti odjednom.
Mehanizam grupiranja je realiziran u klasi ThreadGroup. Svaka nit pripada
određenoj grupi niti. Prilikom kreiranja nove niti možemo pustiti izvršni sustav da ju
automatski dodjeljuje određenoj grupi ili možemo sami specificirati grupu u koju nit
želimo staviti. Kada je neka nit pridružena određenoj grupi ne može se više seliti u
neku drugu grupu.

1.24.1 Pridruživanje osnovnoj grupi


Ako kreiramo nit bez specificiranja grupe u konstruktoru onda izvršni sustav stavlja nit
u grupu koju zovemo trenutna grupa. Kada se izvršni sustav pokreće onda se kreira
grupa main. U tu grupu se smještaju sve nove niti osim u slučaju kada je drugačije
specificirano. Npr. ako stvorimo novu nit u appletu, može se desiti da se nit stavlja u
neku drugu grupu različitu od main. To ovisi o pregledniku u kojem gledamo stranicu.
Konkurentnost u Javi 77

Većina programa ignoriraju grupe niti i sve prepuštaju izvršnom sustavu. Ako program
pokreće puno niti, logično je da ih grupiramo jer je tako lakše manipulirati jednom
grupom, nego sa svakom niti posebno. Grupe niti vide se u razvojnom okruženju
Eclipse kada pokrenemo program u modu Debug i program se zaustavi na točki
prekida.

1.24.2 Klasa ThreadGroup


Klasa ThreadGroup posjeduje nekoliko vrsta metoda za manipuliranje nitima. To su:
• metode za upravljanje podgrupama i nitima,
• metode za čitanje i postavljanje osobina pojedinih grupa niti,
• metode za manipuliranje svim nitima unutar jedne grupe,
• metode za ograničavanje pristupa pojedinim nitima unutar grupe.
Za više informacija pogledajte API.

1.25 Prioriteti niti


Niti se izvode konkurentno na računalu s jednim procesorom. Zbog jednog procesora
dvije ili više niti se ne mogu istovremeno izvoditi. Konkurentnost je riješena tako da se
određuju prioriteti niti koje se izvode i na osnovu tih prioriteta se dodjeljuje određena
količina procesorskog vremena određenoj niti. Taj postupak zove se scheduling. U
Javin izvršni sustav je ugrađen jednostavni algoritam s fiksnim prioritetima za
raspoređivanje procesorskog vremena.
Kada se nit kreira dodjeljuje joj se prioritet niti koja ju je kreirala. Prioritet niti se
može promijeniti metodom setPriority. Prioriteti su cijeli brojevi koji mogu imati
vrijednosti između MIN_PRIORITY i MAX_PRIORITY. To su konstante koje su
definirane u klasi Thread. Veći broj ima veći prioritet. Kada izvršni sustav odlučuje o
niti koja će se izvoditi u određeno vrijeme onda izabere nit koja je u stanju Runnable i
koja ima najveći prioritet. Samo kada se nit zaustavi, pozove metoda yield ili se
nalazi u Not Runnable, može se izvesti nit s nižim prioritetom.
Ako dvije niti istog prioriteta čekaju na izvođenje onda algoritam bira koju će prvu
izvesti. Izabrana nit će se izvoditi dok jedan od slijedećih uvjeta ne bude ispunjen:
• nit višeg prioriteta ne postane Runnable,
• pozove metodu yield ili metoda run završi s izvođenjem,
• na sustavima koji podržavaju time-slicing (raspodjela vremena na sitne
intervale i dodjeljivanje tih intervala nitima) dok se ne potroši vrijeme koje je
dodijeljeno toj niti.
Tek tada se daje šansa drugoj niti da se izvede. Ako sustav na kojem se izvode niti
nema time-slicing onda je potrebno nakratko zaustaviti nit da bi se dala šansa drugim
nitima istog prioriteta. Za postizanje takvog efekta koristi se metoda yield u klasi
Thread, a pozivamo ju iz metode run.

1.26 Sebične niti


Sebične niti su one niti koje ne dozvoljavaju drugim nitima da dobiju dio vremena za
izvođenje. Posljedica sebičnih niti kada sustav nema ugrađen time-slicing je da se
stalno izvršava jedna nit (osim u slučajevima kada imamo niti s višim prioritetom).
Slijedi primjer sebične niti.
public class SelfishThread
extends Thread {
Konkurentnost u Javi 78

public void run() {


for (int i = 0; i < 5; i++) {
System.out.println("broj " + i);
}
System.out.println("Gotovo brojenje");
}

public static void main(String[] args) {


new SelfishThread().start();
new SelfishThread().start();
}
}
Kada imamo sustav bez mehanizma time-slicing ispis je sljedeći.
broj 0
broj 1
broj 2
broj 3
broj 4
Gotovo brojenje
broj 0
broj 1
broj 2
broj 3
broj 4
Gotovo brojenje
Ispis kada imamo time-slicing može biti sljedeći.
broj 0
broj 1
broj 0
broj 2
broj 1
broj 3
broj 2
broj 4
broj 3
Gotovo brojenje
broj 4
Gotovo brojenje
Pristojna nit mora omogućiti drugim nitima dio vremena i to se radi pomoću metode
yield. Kada nit želi prpustiti izvođenje drugoj niti s istim prioritetom onda pozove
metodu yield koja zaustavi trenutnu nit i stavlja ju na kraj repa čekanja na
izvođenje. Sljedeći primjer pokazuje kako izgleda pristojna nit.
public class PoliteThread
extends Thread {

public void run() {


for (int i = 0; i < 5; i++) {
System.out.println("broj " + i);
yield();
}
System.out.println("Gotovo brojenje");
}
Konkurentnost u Javi 79

public static void main(String[] args) {


new PoliteThread().start();
new PoliteThread().start();
}
}
Ispis pristojnih niti je sljedeći.
broj 0
broj 0
broj 1
broj 1
broj 2
broj 2
broj 3
broj 3
broj 4
broj 4
Gotovo brojenje
Gotovo brojenje

1.27 Pravedan sustav


U programu u kojem se nekoliko niti "bori" za resurse mora se obratiti pozornost na to
da se resursi ravnomjerno rasporede. Sustav je pravedan ako svaka nit dobije
dovoljno pristupa resursima tako da sve niti podjednako napreduju u svojim
poslovima. U pravednom sustavu nema stagnacije niti i zastoja (deadlocks). Stagnacija
se događa kada je nekoliko niti blokirano zbog pristupa resursima i ne mogu
napredovati. Deadlock je za razliku od stagniranja kada dvije ili više niti čekaju na
uvjet koji se ne može zadovoljiti. To se obično događa kada dvije ili više niti čekaju
jedna na drugu da oslobode resurse koje su dobile (poznati problem filozofa koji
večeraju).

1.28 Nitna sigurnost


Objekt čije se vrijednosti atributa nalaze u ispravnom stanju bez obzira na broj niti
koje koriste taj objekt je nitno siguran objekt. Pošto Java za razmjenu podataka
između niti koristi dijeljenu memoriju tj. dijeljene objekte vrlo je važno osigurati nitnu
sigurnost jer se u protivnom može dogoditi neispravan rad programa. Pogledajmo
primjer jedne nitno nesigurne klase.
public class Point {
private int x,y;

public Point() {
}

public void setPoint(int x, int y) {


this.x = x;
this.y = y;
}

public String toString() {


return "(" + x + "," + y + ")";
}
}
Konkurentnost u Javi 80

Ovdje je klasa Point koja ima dva atributa (x i y koordinatu točke). Klasa ima dvije
metode: setPoint za postavljanje vrijednosti x i y koordinata i metodu toString
koja vraća tekst koji predstavlja vrijednosti atributa u klasi. Slijedi primjer niti koja
postavlja vrijednosti koordinata u klasi Point.
public class ThreadSetter extends Thread {
private Point point;
int start;

public ThreadSetter(Point p, int s) {


point = p;
start = s;
}
public void run() {
for (int i = start; i < 5 + start; i++) {
point.setPoint(i,i);
String str = point.toString();
System.out.println("nit" + start + " " + str);
}
}
}
Klasa ThreadSetter u svojoj metodi run u petlji postavlja 5 vrijednosti koordinata
gdje su x i y jednaki. Slijedi primjer korištenja ovih klasa.
public class Main {
public static void main(String[] args) {
Point p = new Point();
new ThreadSetter(p, 0).start();
new ThreadSetter(p, 10).start();
}
}
Ovdje smo stvorili jedan objekt iz klase Point i pokrenuli dvije niti ThreadSetter
koje su dobile referencu na objekt klase Point. Mogući ispis je sljedeći.
nit0 (0,0)
nit10 (10,10)
nit0 (1,1)
nit10 (11,11)
nit10 (12,12)
nit0 (2,2)
nit0 (3,3)
nit0 (4,4)
nit10 (4,13)
nit10 (14,14)
Pošto obje pokrenute niti postavljaju vrijednost x i y koordinate na jednake vrijednosti
onda postavljanje na vrijednost 4 i 13 (vidi predzadnji redak ispisa) ne bi smjelo biti
moguće. Kako se to dogodilo pokazuje sljedeći slijedni dijagram (slika 7.2).
Konkurentnost u Javi 81

nit0:ThreadSetter p:Point nit10:ThreadSetter

setPoint(13,13)

x=13

setPoint(4,4)

x=4
y=4

toString()

return "(4,4)"

y=13

toString()

return "(4,13)"

Slika 7.2 - Primjer nitne nesigurnosti

U ovom primjeru vidimo da je nit 10 pozvala metodu setPoint s vrijednostima 13 i


13. Nakon izvršavanja postavljanja vrijednosti u atribut x zbog mehanizma time-
slicing je prekinuto izvođenje niti 10 i nastavila se izvršavati nit 0. Nit 0 poziva
metodu setPoint s vrijednostima 4 i 4. Te vrijednosti se postavljaju u atribute x i y i
izvršavanje metode setPoint se završava. Nakon toga nit 0 poziva metodu
toString koja vraća tekst „(4,4)“. Nakon toga se zbog mehanizma time-slicing
zaustavlja nit 0 i nastavlja izvođenje niti 10 koja postavlja vrijednost y na 13 i zatim
metoda toString vraća trenutne vrijednosti atributa koje su 4 i 13. Kako niti jedna
nit nije postavljala vrijednosti u toj kombinaciji to znači da je došlo do greške.
Da bi se osigurala nitna sigurnost možemo se koristiti sljedećim mehanizmima:
• sinkronizacija kritičnih odsječaka – u ovom promjeru metoda setPoint,
• korštenje objekta koji nije moguće promijeniti nakon kreiranja (immutable) ili
• korištenje nitno sigurnog omotača (thread-safe wrapper) za klase koje ne
možemo mijenjati i napraviti sinkronizaciju odsječaka (koristi se kod struktura
podataka).

1.29 Sinkronizacija niti


Često niti trebaju dijeliti podatke. Npr. imamo nit koja zapisuje podatke u datoteku, a
istovremeno druga nit čita podatke iz datoteke. Kada niti dijele podatke potrebno je
sinkronizirati kritične odsječke tako da ne dođe do konflikta. Dijelovi koda koji
pristupaju istom objektu iz različitih niti nazivaju se kritični odsječci. Za sinkronizaciju
kritičnih odsječaka potrebno je napraviti sljedeće:
• staviti atribute da su privatni,
• identificirati kritične odsječke i
• označiti kritične odsječke da su sinkronizirani.
Konkurentnost u Javi 82

Mehanizam sinkronizacije niti se temelji na konceptima zaključavanja objekta


(označavanja kritičnih odsječaka da su sinkronizirani) i koordinacije izvođenja pomoću
monitora (atributa koji ograničavaju redoslijed izvršavanja).

1.30 Zaključavanje
U Javi kritični odsječak može biti dio koda ili metoda. Kritični odsječak se označava
ključnom riječi synchronized. Kada je kritični odsječak čitava metoda onda se ona
označi (vidi sljedeći kod).
public synchronized void methodName() {
// kritični odsječak
}
Kada je kritični odsječak dio neke metode onda se koristi sinkronizirani blok (vidi
sljedeći kod).
synchronized (object) {
// kritični odsječak
}
Kada neka nit izvodi kod unutar kritičnog odsječka onda je taj objekt zaključan i ključ
ima ta nit. Razlika između označavanja metode i bloka je u tome što kod korištenja
metode, objekt nad kojim se koristi zaključavanje je uvijek this, a kod korištenja
bloka možemo koristiti i druge objekte za zaključavanje.

[objekt odključan] [objekt zaključan]

zaključaj [nit ima ključ] [nit nema ključ]


objekt

izvodi zaustavi
izvodi kod nit
kod

[ulazak u novi sink. blok]

[izlazak iz sinkroniziranog bloka]

odključaj
objekt

Slika 7.3 - Obrada sinkroniziranog bloka

Na slici 7.3 je prokazana obrada sinkroniziranog bloka. Prije ulaska neke niti u
izvođenje sinkroniziranog bloka (kritičnog odsječka) provjerava se je li objekt
zaključan. Ako nije zaključan onda se objekt zaključava i izvodi se kod. Ako se i
izvođenju koda ulazi u drugi kritični odsječak onda se ponovno izvršava ovakav
dijagram. Prije izlaska iz kritičnog odsječka objekt se otključava. Ako je objekt
zaključan provjerava se imali trenutna nit ključ. Ako ključ ima nit koja ulazi u odsječak
onda se izvođenje nastavlja. Ako nit nema ključ onda se ta nit zaustavlja.
Konkurentnost u Javi 83

Da bi klasa Point bila nitno sigurna potrebno je odrediti kritične odsječke i označiti
ih s ključnom riječi synchronized. Kritični odsječci u klasi Point su metoda
setPoint i metoda toString. Izmijenjeni kod je na kodu koji slijedi.
public class Point {
private int x,y;

public Point() {
}

public synchronized void setPoint(int x, int y)


{
this.x = x;
this.y = y;
}

public synchronized String toString()


{
return "(" + x + "," + y + ")";
}
}
Ispis izvršavanja izmijenjenog koda može biti ovakav.
nit0 (0,0)
nit10 (10,10)
nit0 (1,1)
nit10 (1,1)
nit10 (12,12)
nit0 (2,2)
nit0 (3,3)
nit0 (4,4)
nit10 (13,13)
nit10 (14,14)
Ovdje vidimo da više nema krivog postavljanja vrijednosti, ali imamo u četvrtom retku
(nit10 (1,1)) ispis u kojem nit 10 ispisuje brojeve koje je stavila nit 0. Prikaz kako
je došlo do toga je na dijagramu na slici 7.4.
Konkurentnost u Javi 84

nit0:ThreadSetter p:Point nit10:ThreadSetter

setPoint(11,11)

x=11
y=11

setPoint(1,1)

x=1
y=1

toString()

return "(1,1)"

toString()

return "(1,1)"

Slika 7.4 – Ispravno zaključavanje

Kod ispravno radi, ali je ispis krivi jer se između zapisivanja i čitanja podatka
(metoda setPoint i toString) zapisala drugu vrijednost.

1.31 Primjer sinkronizacije


U slijedećem primjeru se koriste četiri klase. Jedna je klasa Producer koja generira
brojeve, sprema ih u objekt iz klase Storage i ispisuje generirani broj. Nakon toga
objekt generator prekida izvođenje na slučajni broj milisekundi između 0 i 100 prije
nego krene u drugu iteraciju. Generator izvodi 10 iteracija i onda prekida izvođenje.
public class Producer extends Thread {
private Storage s;
private int num;

public Producer(Storage s, int i) {


this.s = s;
num = i;
}

public void run() {


for(int i = 0; i < 10; i++) {
s.put(i);
System.out.println("Producer " + num + " stavio: " + i);
try {
sleep((int)(Math.random()*100));
} catch (InterruptedException e) {
}
}
}
}
Objekt Consumer troši, što brže, sve brojeve koje Producer generira i stavlja u
objekt Storage.
public class Consumer extends Thread {
private Storage s;
private int num;
Konkurentnost u Javi 85

public Consumer(Storage s, int i) {


this.s = s;
num = i;
}

public void run() {


int n = 0;
for(int i = 0; i < 10; i++) {
n = s.get();
System.out.println("Consumer " + num + " uzeo: "
+ n);
}
}
}
Ova dva objekta su primjer dijeljenja podataka kroz objekt Storage. Ovdje vidimo
da oba objekta pristupaju podacima kao da sinkronizacija nije potrebna, ali se
sinkronizacija izvodi u metodama put i get objekta Storage. Kada bi se
sinkronizacija zanemarila onda bi: ili Producer generirao previše podataka i gubili
bi se podaci preko kojih se stavljaju novi ili bi objekt Consumer nekoliko puta uzeo
isti podatak. U oba slučaja je rezultat krivi jer bi se svaki podatak trebao prije uzeti
nego se upisuje novi i isto tako se ne bi smio više puta uzeti isti podatak.
Da bi se izbjegle takve situacije potrebno je sinkronizirati stavljanje i uzimanje
podataka. Ovdje se koriste metode wait i notifyAll za sinkronizaciju u
kombinaciji sa zastavicom (uvjetnom varijablom).
public class Main {
public static void main(String[] args) {
Storage s = new Storage();
Producer p = new Producer(s, 1);
Consumer c = new Consumer(s, 1);

p.start();
c.start();
}
}
Ispis:

Producer 1 stavi: 0
Consumer 1 uzeo: 0
Producer 1 stavi: 1
Consumer 1 uzeo: 1
Consumer 1 uzeo: 2
Producer 1 stavi: 2
Producer 1 stavi: 3
Consumer 1 uzeo: 3
Producer 1 stavi: 4
Consumer 1 uzeo: 4
Producer 1 stavi: 5
Consumer 1 uzeo: 5
Consumer 1 uzeo: 6
Producer 1 stavi: 6
Producer 1 stavi: 7
Consumer 1 uzeo: 7
Konkurentnost u Javi 86

Consumer 1 uzeo: 8
Producer 1 stavi: 8
Producer 1 stavi: 9
Consumer 1 uzeo: 9
U ovom primjeru se koriste dva načina sinkronizacije niti. To su korištenje monitora
(uvjetne varijable) i korištenje metoda notifyAll i wait.

1.32 Problemi kod sinkronizacije


Najveći problem je kada se u programu koristi klasa koja nije namijenjena da radi u
konkurentnom okruženju tj. kada nema nikakve sinkronizacije. Drugi problem je kada
je sinkronizacija kriva. Kod krivo postavljene sinkronizacije su krivo postavljeni uvjeti
koji dovode do zastoja (deadlock).
Kada je program dobro napravljen potrebno je osim ispravne sinkronizacije osigurati
i pravedni sustav. Pod tim se podrazumijeva da svaka nit dobije dovoljno pristupa
resursima tako da sve niti napreduju u svojim poslovima tj. da nema stagnacije niti i
zastoja.
Pojam stagnacije (starvation) je kada Javin virtualni stroj ne dodjeljuje vrijeme za
izvođenje nekoj niti. To se obično događa ili zbog lošeg algoritma za raspoređivanje
vremena ili zbog loše programerske prakse npr. obrada složenih i dugih operacija u
obradi događaja u grafičkom sučelju.
Pojam zastoja (deadlock) je kada imamo dvije ili više niti koje trajno čekanju jedna na
drugu. Tipičan primjer je problem filozofa koji večeraju.

1.33 Koordinacija
Koordinacija izvršavanja niti se vrši pomoću monitora. Monitor je zapravo uvjetna
varijabla tj. atribut, koja regulira pristup podacima. Taj mehanizam dozvoljava
pristup podacima samo jednoj niti u određenom vremenskom periodu.
U klasi Storage koju smo koristili u primjeru nalaze se dvije metode označene sa
synchronized: put koja se koristi za promjenu podataka u monitoru i get koja se
koristi za uzimanje podataka iz monitora.
public class Storage {
private int num;
private boolean bEmpty = true;

public synchronized int get() {


while(bEmpty == true) {
try {
wait();
} catch (InterruptedException e) {
}
}
bEmpty = true;
notifyAll();
return num;
}

public synchronized void put(int i) {


while(bEmpty == false) {
try {
wait();
} catch (InterruptedException e) {
}
Konkurentnost u Javi 87

}
num = i;
bEmpty = false;
notifyAll();
}
}
U ovom primjeru se atribut bEmpty slobodno koristi kao oznaka dostupnosti podatka
koji se nalaze u atributu num. Ako je true onda je Producer stavio podatak i on je
dostupan za uzimanje. Ako je false onda je podatak uzet i može se samo staviti
novi podatak.

1.33.1 Metode notifyAll i wait


U objektu Storage se koriste obje metode za sinkronizaciju stavljanja i uzimanja
podataka. Obje metode se mogu pozvati samo od niti koja posjeduje ključ objekta.
Svaki objekt pored ključa ima i skup niti koje su pozvale metodu wait i ne mogu se
nastaviti izvršavati dok ih se na makne iz tog skupa. Metode notify i notifyAll.
Kada se pozove metoda wait nit se zaustavlja i stavlja u skup niti koje su pozvale
metodu wait nad tim objektom. Nakon poziva metode wait ostale niti mogu početi
izvršavati sinkronizirane dijelove blokove/metode u tom objektu (slika 7.5). Metoda
notifyAll miče sve niti iz skupa niti koje su pozvale wait. To se obično poziva
prije izlaska iz metode synchronized. Jedna od niti koje čekaju na oslobođenje
niti će zauzeti objekt, a ostale će ostati u stanju čekanja. Postoji i metoda notify
koja miče samo jednu nit iz skupa niti koje su pozvale metodu wait.Ona se rijetko
koristi jer se kod nje može desiti da niti jedna nit ne nastavi s radom nakon njenog
poziva tj. može doći do zastoja. Zbog toga se koristi metoda notifyAll.

izvodi
kod
[pozvan wait] stavi nit
u skup
wait
[pozvan notify] [ostalo]

zaustavi
makni jednu nit
nit iz skupa wait

[pozvan notifyAll]
zaustavi nit
makni sve
niti iz skupa wait

Slika 7.5 – Algoritam izvođenja metoda wait i notify

Metoda wait zaustavlja nit dok se ne pozove notify nad objektom u kojem je nit
zaustavljena. Metoda wait se koristi u sprezi s metodama notifyAll ili notify.
Pomoću metoda wait i notifyAll se koordiniraju niti u primjeru.
Metoda get ima while petlju koja se izvodi dok atribut bEmpty ima vrijednost
true. Ako je bEmpty true, to znači da generator nije generirao novu vrijednost i
stavio ju u Storage. U tom slučaju poziva se wait koja zaustavlja nit i čeka dok nije
pozvana metoda notifyAll od strane generatorske niti (slika 7.6). Slično se
događa i kod metode put.
Konkurentnost u Javi 88

p:Producer s:Storage c:Consumer

get()

wait()

put(0)

num=1
bEmpty=false

notifyAll()

put(1)

wait()

bEmpty=true

notifyAll()
0

Slika 7.6 – Izvođenje sinkronizacije u primjeru Producer/Consumer

Osim metode wait bez argumenata postoje još dvije s argumentima:


• wait(long timeout) - čeka notify ili dok ne prođe određen broj
milisekundi koji je zapisan u timeout,
• wait(long timeout, int nano) - čeka notify ili dok ne prođe
vrijeme timeout milisekundi + nano nanosekundi.

1.34 Sinkronizacijski mehanizmi


U Javi su od verzije 5.0 dodani napredni mehanizmi za sinkronizaciju konkurentnih
aktivnosti. Ono uvelike olakšavaju razvoj programa koji koriste konkurentnost.

1.34.1 Brojeći semafor


Bojeći semafor je uveo Edsger Dijkstra još 1965. godine. Ovaj mehanizam se koristi
za rezervaciju resursa. Tipičan primjer korištenja je kada imamo više od jednog
resursa i trebamo ih dodijeliti nitima od kojih svaka treba po jedan resurs. Poseban
slučaj brojećeg semafora je kada ima samo jedan resurs (mutex). Primjer jednog
slučaja je kada imamo nekoliko klijenata koji koriste jedan objekt za pristup bazi
podataka, a ograničenje je broj konekcija prema bazi. Pogledajmo primjer koda
brojećeg semafora u Javi.
public class CountingSemaphor
{
private int maxCount;

private List<Thread> threads;

public CountingSemaphor( int i )


{
maxCount = i;
threads = new LinkedList<Thread>();
Konkurentnost u Javi 89

public synchronized void aquire( Thread t )


{
while ( maxCount == 0 )
{
try
{
wait();
}
catch ( InterruptedException ie )
{
}
}
threads.add( t );
maxCount--;
System.out.println( "in aquire" );
printThreads();
}

public synchronized void relese( Thread t )


{
threads.remove( t );
maxCount++;
notifyAll();
System.out.println( "in relese" );
printThreads();
}

private void printThreads()


{
for ( Thread t : threads )
{
System.out.println( t.getName() );
}
}
}
Ovdje imamo konstruktor koji prima broj resursa koji se mogu dodijeliti. Nakon
kreiranja objekta iz klase CountingSemaphor, niti mogu pozivati metodu aquire
za dobivanje resursa. Kada se pozove ta metoda nit ostaje blokirana ukoliko nema
raspoloživih resursa, a kada se oslobode ili ih ima dovoljno onda se nit vraća iz te
metode. Nakon toga nit može izvršavati kod koji koristi zahtijevani resurs. Kada je nit
gotova s korištenjem resursa onda ga mora osloboditi. To se vrši pozivanjem metode
release. Primjer korištenja brojećeg semafora je u nastavku (klasa
CoungtingThread).
public class CountingThread
extends Thread
{
CountingSemaphor cs;

public CountingThread( String name, CountingSemaphor cs )


{
super( name );
this.cs = cs;
Konkurentnost u Javi 90

public void run()


{
for ( int i = 0; i < 10; i++ )
{
cs.aquire( this );
System.out.println( getName() + " imam konekciju " + i +
". put" );
try
{
wait( (int) ( Math.random() * 500 ) );
}
catch ( Exception ex )
{
}
cs.relese( this );
}
}

public static void main( String[] args )


{
CountingSemaphor cs = new CountingSemaphor( 3 );
for ( int i = 0; i < 10; i++ )
{
CountingThread ct = new CountingThread( "nit" + i, cs );
ct.start();
}
}
}
Metoda main pokreće 10 niti koje po deset puta rezerviraju resurs i nakon obrade
ga oslobađaju. Brojeći semafor ima na raspolaganju samo 3 resursa.

1.34.2 Sinkroniziranje kolekcija podataka


Kolekcije podataka su vrlo ranjivi objekti što se tiče konkurentnog pristupa njima. U
općenitom slučaju potrebno je sinkronizirati sve metode za dohvaćanje i zapisivanje
podataka u njih. Ako se napravi takva sinkronizacija onda je često kolekcija
podataka usko grlo i nemoguće je napraviti neku vrstu optimizacije.
Često se liste koriste kao repovi tj. podaci se u listu stavljaju na kraj liste, a uzimaju s
početka. U takvim slučajevima je moguće napraviti optimizaciju tako da nema
zaključavanja objekta.
Drugi problem koji se javlja kod kolekcija je to što se i kod korištenja sinkroniziranih
metoda može dogoditi asinkroni pristup podacima koji može rezultirati greškom. Taj
slučaj je kada se koristi iterator za prolazak kroz elemente kolekcije, a druga nit
može pozvati metodu za promjenu sadržaja kolekcije. Sljedeći primjer pokazuje taj
slučaj.
public class IteratorProblem
{
public static void main( String[] args )
{
List<String> list = new LinkedList<String>();
list.add( "prvi" );
Konkurentnost u Javi 91

list.add( "drugi" );

Iterator iter = list.iterator();


System.out.println( iter.next() );

list.add( "treci" );
System.out.println( iter.next() );
}
}
Ovdje se stvara vezana lista u koju se dodaju dva elementa. Nakon toga se stvara
iterator i dohvaća prvi element liste pomoću tog iteratora. Nakon toga se dodaje
element u listu pomoću metode add. Nakon toga se pomoću iteratora dohvaća
sljedeći element. U metodi next događa se bacanje iznimke jer je u međuvremenu
promijenjen sadržaj liste. Ispis (vidi nastavak) to potvrđuje.
prvi
Exception in thread "main" java.util.ConcurrentModificationException
at
java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:
617)
at java.util.LinkedList$ListItr.next(LinkedList.java:552)
at
hr.fer.tel.kp.iterator.IteratorProblem.main(IteratorProblem.java:32)
Problem konkurentnih iteratora može se riješiti kako koristimo:
• sinkronizirane kolekcije i iteratore,
• kopiranje podataka prilikom stvaranja iteratora ili
• kopiranje podataka prilikom promjene u kolekciji (ovo je korisno kod puno
čitanja i malo pisanja).
Sljedeći primjer pokazuje kako se mogu koristiti sinkronizirane kolekcije i iteratori.
public class AdderThread
extends Thread
{
private List<String> list;

public AdderThread( List<String> list )


{
this.list = list;
}

public void run()


{
list.add( "treci" );
System.out.println( "added" );
}
}
Klasa AdderThread prima kao argument konstruktora listu u koju u niti (metoda
run) dodaje jedan element.
public class SynchronizedIterator
extends Thread
{
private final List list;
Konkurentnost u Javi 92

public SynchronizedIterator( List list )


{
this.list = list;
}

public void run()


{
synchronized ( list )
{
Iterator iter = list.iterator();
while ( iter.hasNext() )
{
System.out.println( iter.next() );
Thread.yield();
}
}
}
}
Klasa SynchronizedIterator prime listu u konstruktoru. U niti (metoda run)
stvara iterator i prolazi kroz sve elemente liste. Ono što je ovdje bitno da bi iterator
ispravno radio je prolaziti kroz elemente u sinkroniziranom bloku koji kao objekt
sinkronizacije ima listu. U protivnom bi se dogodila iznimka. Slijedi primjer korištenja
ovih klasa.
public class SynchronizedIteratorMain
{

public static void main( String[] args )


{
List<String> list = Collections.synchronizedList(
new LinkedList<String>() );

list.add( "prvi" );
list.add( "drugi" );

Thread iter = new SynchronizedIterator( list );


iter.start();

AdderThread adder = new AdderThread( list );


adder.start();
}
}
Kako što vidimo osim sinkronizacije iteratora potrebno je još koristiti i sinkroniziranu
verziju liste koja se dobije pozivom statička metode synchronizedList u klasi
Collections.

1.34.3 Sinkronizacija repa


Već je prije spomenuto da se u slučaju korištenja kolekcije koja predstavlja rep
(uzimanje s početka i stavljanje na kraj) moguće napraviti optimizaciju za konkurentno
korištenje tako da se nit koja koristi rep ne blokira (privremeno zaustavlja).
Za takav slučaje je u Javi napravljeno sučelje
java.util.concurrent.BlockingQueue. Kako koristimo implementaciju s polje
onda možemo definirati kapacitet repa. Korištenje iteratora ne baca iznimku. Postoji
nekoliko vrsta implementacija repa:
Konkurentnost u Javi 93

• blokirajući rep (ArrayBlockingQueue, LinkedBlockingQueue) – nakon


popunjavanja kapaciteta repa stavljanje podataka blokira nit, a isto tako kod
uzimanja podatka iz praznog repa također se blokira nit,
• rep s kašnjenjem (DelayQueue) – kod stavljanja se može navesti vrijeme
koliko se element neće moći izvaditi iz repa, a kod uzimanja se može
definirati koliko se maksimalno čeka element,
• rep s prioritetima (PriorityBlockingQueue) – elementi se u repu sortiraju
prema prirodnom rasporedu ili pomoću sučelja Comparable,
• sinkronizirani rep (SynchronousQueue) - zapravo nije rep jer se prilikom
stavljanja nit blokira dok druga nit ne uzme podatak iz repa.
Slijedi primjer korištenja repa.
public class QueueProducer
extends Thread
{
private BlockingQueue<Integer> queue;

public QueueProducer( BlockingQueue<Integer> queue )


{
this.queue = queue;
}

@Override
public void run()
{
try
{
for ( int i = 0; i < 10; i++ )
{
queue.put( i );
System.out.println("Stavio: " + i);
}
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
}
}
Klasa QueueProducer stavlja 10 podataka u rep.
public class QueueConsumer
extends Thread
{
private BlockingQueue<Integer> queue;

public QueueConsumer( BlockingQueue<Integer> queue )


{
super();
this.queue = queue;
}

@Override
public void run()
Konkurentnost u Javi 94

{
try
{
while ( true )
{
int data = queue.take();
System.out.println( "Uzeo: " + data );
}
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
}
}
Klasa QueueConsumer uzima podatke iz repa u beskonačnoj petlji.
public class QueueMain
{
public static void main( String[] args )
throws InterruptedException
{
BlockingQueue<Integer> queue = new
ArrayBlockingQueue<Integer>(3);

QueueProducer producer = new QueueProducer(queue);


producer.start();

Thread.sleep( 3000 );

QueueConsumer consumer = new QueueConsumer(queue);


consumer.start();
}
}
Klasa QueueMain pokreće program tako da stvori rep, stvori dvije niti. Nit iz klase
QueueProducer stavlja podatka u rep. Nakon pokretanja te niti glavna niti pričeka
3 sekunde tako da se vidi da se nit koja stavlja privremeno zaustavi. Nakon 3
sekunde stvara se druga nit (klasa QueueConsumer) i odmah pokreće. Mogući sipis
je sljedeći.
Stavio: 0
Stavio: 1
Stavio: 2
Uzeo: 0
Uzeo: 1
Uzeo: 2
Uzeo: 3
Stavio: 3
Stavio: 4
Uzeo: 4
Stavio: 5
Uzeo: 5
Stavio: 6
Uzeo: 6
Stavio: 7
Konkurentnost u Javi 95

Uzeo: 7
Stavio: 8
Uzeo: 8
Stavio: 9
Uzeo: 9

1.34.4 Izmjenjivač podataka


Izmjenjivač se koristi kada dvije niti žele razmijeniti podatke. Kada jedna nit pozove
metodu za izmjenu podataka (metoda exchange) ona se blokira dok i druga nit ne
pozove istu metodu. Tada se podaci zamijene i niti se nastavljaju izvoditi. Rezultat
izvođenja metode exchange je podatak od druge niti. Pogledajmo primjer.
public class ExchangerThread
extends Thread
{
int start, end;

Exchanger<Integer> exchanger;

public ExchangerThread( int start, int end,


Exchanger<Integer> exchanger )
{
this.start = start;
this.end = end;
this.exchanger = exchanger;
}

@Override
public void run()
{
try
{
for ( int i = start; i < end; i++ )
{
int data = exchanger.exchange( i );
System.out.println("poslao " + i + " primio " + data);
}
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
}
}
Klasa ExchangerThread u petlji razmjenjuje podatke s drugom niti pozivom
metode exchange.
public class ExchangerMain
{
public static void main( String[] args )
{
Exchanger<Integer> exchanger = new Exchanger<Integer>();

ExchangerThread thread1 =
new ExchangerThread( 0, 10, exchanger );
Konkurentnost u Javi 96

thread1.start();

ExchangerThread thread2 =
new ExchangerThread( 20, 30, exchanger );
thread2.start();
}
}
Ovdje se stvara objekt Exchanger koji se predaje nitima koje razmjenjuju podatke.
Ovdje je ispis izvršavanja koda.
poslao 0 primio 20
poslao 20 primio 0
poslao 21 primio 1
poslao 1 primio 21
poslao 2 primio 22
poslao 22 primio 2
poslao 23 primio 3
poslao 3 primio 23
poslao 4 primio 24
poslao 24 primio 4
poslao 25 primio 5
poslao 5 primio 25
poslao 6 primio 26
poslao 26 primio 6
poslao 27 primio 7
poslao 7 primio 27
poslao 8 primio 28
poslao 28 primio 8
poslao 29 primio 9
poslao 9 primio 29

1.34.5 Prepreka
Prepreka (barrier) je sinkronizacijska točka u kojoj se sinkronizira više niti. Barijera je
implementacija spajanja (join) i grananja (fork) iz dijagrama aktivnosti. Svaka nit koja
se treba sinkronizirati treba pozvati metodu await u prepreci u kojoj se blokira.
Kada specificiran broj niti pozove metodu await u prepreci onda se sve niti
oslobađaju i nastavljaju svoje izvođenje. Broj niti koje trebaju pozvati metodu await
da bi se nastavile izvoditi specificira se prilikom konstruiranja prepreke.
Implementacija barijere je u Javi implementirana klasom CyclicBarrier. Slijedi
primjer korištenja.
public class BarrierThread
extends Thread
{
CyclicBarrier barrier;

public BarrierThread( String name, CyclicBarrier barrier )


{
super( name );
this.barrier = barrier;
}

@Override
public void run()
{
Konkurentnost u Javi 97

try
{
for ( int i = 0; i < 5; i++ )
{
System.out.println( getName() + ": " + i );
if ( i == 2 )
{
barrier.await();
System.out.println(getName() + ": nakon nastavka");
}
}
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
catch ( BrokenBarrierException e )
{
e.printStackTrace();
}
}
}
Klasa BarrierThread u konstruktoru prime referencu na barijeru, a u niti poziva
metodu await kada se brojač izjednači s 2. U tom trenutku se blokira. Kada sve niti
pozovu istu metodu u barijeri onda se nastavlja izvođenje svih niti. Slijedi pokretanje
niti.
public class BarrierMain
{

public static void main( String[] args )


{
CyclicBarrier barrier = new CyclicBarrier( 3 );

new BarrierThread( "nit1", barrier ).start();


new BarrierThread( "nit2", barrier ).start();
new BarrierThread( "nit3", barrier ).start();
}
}
Metoda main stvara barijeru s u kojoj se tri niti sinkroniziraju. Nakon toga se stvaraju
i pokreću tri niti. Mogući ispis je sljedeći.
nit1: 0
nit1: 1
nit2: 0
nit2: 1
nit2: 2
nit1: 2
nit3: 0
nit3: 1
nit3: 2
nit3: nakon nastavka
nit3: 3
nit3: 4
nit2: nakon nastavka
Konkurentnost u Javi 98

nit2: 3
nit2: 4
nit1: nakon nastavka
nit1: 3
nit1: 4

1.34.6 Uzorak zaključavanja


Uzorak zaključavanja je vrlo sličan korištenju sinkroniziranih blokova u kombinaciji s
metodama wait i notify/notifyAll. Sučelje Lock definira metode za
zaključavanje, a klasa ReentrantLock je implementacija sučelja. Metoda
newCondition vraća novi objekt koji implementira sučelje Condition. U tom
sučelju su definirane metode koje mogu zaustaviti nit (metoda await) i pokrenuti
zaustavljene niti (metode signal/signalAll). Ove dvije metode su ekvivalentne
metodama wait i notify/notifyAll. Pogledajmo primjer korištenja ovog
mehanizma u klasi Storage iz prethodnog primjera.
public class Storage
{
final private Lock lock = new ReentrantLock();
final private Condition numberPut = lock.newCondition();
final private Condition numberGot = lock.newCondition();

private int num;

private Condition condition = numberGot;

public int get()


{
lock.lock();
int temp = 0;
try
{
while ( condition == numberGot )
numberPut.await();

temp = num;
condition = numberGot;

numberGot.signal();
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
return temp;
}

public void put( int i )


{
lock.lock();
try
Konkurentnost u Javi 99

{
while ( condition == numberPut )
numberGot.await();

num = i;
condition = numberPut;

numberPut.signal();
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
}
Kako što vidimo iz primjera prvo se mora pozvati metoda lock koja zaključava
korištenje locka. Nakon toga se mora staviti try/finally blok i u finally boku
staviti poziv metode unlock. To je jedini način da se osigura da se u slučaju bacanja
iznimke uvijek oslobodi lock. Unutar bloka try možemo koristi uvjete. Kada se
pozove metoda await u uvjetu onda se trenutna nit zaustavlja i oslobađa se lock
tako da druge niti mogu koristiti lock i uvjete.
Korištenje uzorka zaključavanja je opasnije nego korištenje ključne riječi
synchronized. Ako se zaboravi staviti poziv metode unlock u bloku finally
kod će izgledati kao da radi ispravno, ali to je zapravo greška koja se može
manifestirati kada se dogodi iznimka. Uzorak zaključavanja je napredni alat trebalo
bi ga se koristiti samo u situacijama kada se sinkronizirani blok ne može koristiti.
Savjet je da se uzorak zaključavanja koristi samo kada se trebaju napredne funkcije
kao što su: vremenske kontrole u uvjetima, neblokirajuće zaključavanje, pravedno
aktiviranje niti koje čekaju, neprekidajuće čekanje i sl.

1.35 Recikliranje niti


U je Javi, za razliku od Erlanga, stvaranje i uništavanje konkurentnih aktivnosti (niti)
skupo tj. troši puno resursa i može trajati po nekoliko stotina milisekundi. Zbog toga se
u Javi koriste bazeni niti. Kada neki dio programa treba nit onda iz bazena uzme nit
i kada je gotov onda ju vrati natrag u bazen. Na taj način se niti recikliraju.
Drugi razlog za korištenje bazena niti je što se na taj način na jednom mjestu može
upravljati nitima. Npr. mogu se definirati strategije stvaranja i na taj način ograničiti
maksimalni broj korisnika neke mrežne usluge. Isto tako se programer ne mora brinuti
o stvaranju niti već treba napraviti poslove i poslati ih na obradu. Kada i koja nit će
to izvršiti nije mu važno. Ovakav način programiranja daje elegantnije i jednostavnije
programe.
Treći razlog za korištenje bazena niti su performanse. Ako se dobro podesi strategija
korištenja niti onda korisnik može imati bolji odziv sustava. Uzmimo za primjer da
trebamo obraditi 3 zahtjeva koja su stigla u isto vrijeme na obradu. Jedan od način
na koji to možemo obraditi je da pokrenemo 3 niti od kojih svaka radi obradu jednog
zahtjeva. Uzmimo da obrada jednog zahtjeva traje 3 sekunde. Pošto sustav koristi
mehanizam podjele vremena po nitima (time-slicing)sve tri niti će završiti otprilike u
podjednako vrijeme (za 9 sekundi). Dakle, svaki korisnik vidi koliko je trebalo da se
obradi njegov zahtjev a to je 9 sekundi. Kako je svaki korisnik čekao 9 sekundi onda
Konkurentnost u Javi 100

je ukupno prosječno vrijeme čekanja korisnika 9 sekundi. Da smo imali samo jednu nit
koja je trebala obaviti sve te poslove onda bi ta nit prvi zahtjev obradila za 3
sekunde, drugi za sljedeće 3 sekunde i zadnji za sljedeće 3 sekunde. Dakle, prvi
korisnik bi čekao 3 sekunde, drugi 6 sekundi, a treći 9 sekundi. U tom slučaju je
prosječno vrijeme čekanja korisnika 6 sekundi. U ovom slučaju korisnik ima percepciju
da sustav brže radi iako je ista količina posla obrađena. Zaključak koji se može iz
ovoga napraviti je da je u slučaju jednog procesora na računalu najbolje koristiti
jednu nit.
Ovaj zaključak je točan ukoliko ta nit koja obrađuje zahtjeve ne čeka. Npr. tipičan
zahtjev u web-aplikaciji zahtjeva dohvaćanje podataka iz baze podataka. Obično se
baza podataka nalazi na nekom drugom računalu. To znači da će nit za vrijeme
obrade zahtjeva poslati upit u bazu i čekati odgovor. Dok ona čeka odgovor
procesor nije zaposlen pa je logično da zahtjeve koji čekaju počne obrađivati druga
nit. Ta druga nit isto tako šalje upit bazi i treba čekati odgovor i ako slanja upita
obje niti čekaju onda je potrebno koristiti još jednu nit. Određivanje koliko je niti
potrebno koristiti tako da korisnici što kraće čekaju nije jednostavno. Ako imamo
dobre parametre onda možemo izračunati koliko nam je potrebno niti, a ako nemamo
onda treba eksperimentalno izmjeriti pomoći stresnih testova.
U Javi postoje 3 sučelja koja služe za recikliranje niti:
• sučelje Executor – služi za pokretanje poslova,
• sučelje ExecutorService – nasljeđuje sučelje Executor i ime metode za
upravljanje poslovima (npr. metode za gašenje bazena niti, metoda submit
za izvršavanje posla koji asinkrono vraća rezultat, metoda invokeAll koja
pokreće izvršavanje kolekcije poslova, …) i
• sučelje ScheduledExecutorService – nasljeđuje sučelje
ExecutorService i dodaje metode za pokretanje poslova s počekom.
Konkretne implementacije ovi sučelja mogu se dobiti pozivom statičkih metoda u klasi
Executors ili stvaranjem konkretnih klasa koje implementiraju sučelja (npr.
ScheduledThreadPoolExecutor i ThreadPoolExecutor).
Implementacije sučelja mogu imati ugrađene strategije. Moguće strategije su sljedeće:
• sve se izvodi u jednoj niti (Executors. newSingleThreadExecutor()),
• fiksan broj niti (Executors. newFixedThreadPool(int nThreads)),
• promjenjiv broj niti (Executors. newCachedThreadPool()),
o definiranje osnovnog, minimalnog i maksimalnog broja niti,
o na početku se kreira minimalan broj niti, nove niti se kreiraju tek kada
je pun rep,
o niti nove niti se kreiraju dok nije dosegnut maksimum,
o ako nakon nekog vremena (keep alive time) niti nisu aktivne smanjuje se
broj niti.
o višak zahtjeva se stavlja u rep (sinkronizirani rep, ograničen ili
neograničen),
o odbacivanje zahtjeva u slučaju dosegnutog maksimuma (broj niti i rep)
može biti:
§ bacanje iznimke,
§ Executor izvodi zahtjev – smanjenje dolaska broja zahtjeva,
§ zahtjev se ignorira ili
§ najstariji zahtjev se ignorira.

Pogledajmo primjer korištenja bazena niti. Prvo moramo definirati posao koji će se
raditi. To je stavljenu u klasu MyTask.
Konkurentnost u Javi 101

public class MyTask


implements Runnable
{
private int id;

public MyTask( int id )


{
this.id = id;
}

public void run()


{
try
{
System.out.println(Thread.currentThread().getName() +
": task: " + id + " početak");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() +
": task: " + id + " kraj");
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
}
}
Klasa koja predstavlja posao treba implementirati sučelje Runnable. Ova klasa
preko konstruktora dobije svoj identifikator i u metodi run ispiše da je počela raditi,
svoj identifikator i ime niti koja je pokrenula ovaj posao. Slijedi primjer bazena niti s
fiksnim brojem niti.
public class SimpleExecutorMain
{

public static void main( String[] args )


{
ExecutorService pool = Executors.newFixedThreadPool( 2 );
for ( int i = 0; i < 10; i++ )
{
pool.execute( new MyTask(i) );
}
}
}
U metodi main se stvara bazen niti pomoću poziva metode newFixedThreadPool
i klasi Executors. Nakon toga se bazenu šalju zahtjevi za izvršavanjem 10 poslova
(metoda execute). Ispis je sljedeći.
pool-1-thread-1: task: 0 početak
pool-1-thread-2: task: 1 početak
pool-1-thread-1: task: 0 kraj
pool-1-thread-2: task: 1 kraj
pool-1-thread-1: task: 2 početak
pool-1-thread-2: task: 3 početak
pool-1-thread-1: task: 2 kraj
pool-1-thread-2: task: 3 kraj
Konkurentnost u Javi 102

pool-1-thread-1: task: 4 početak


pool-1-thread-2: task: 5 početak
pool-1-thread-2: task: 5 kraj
pool-1-thread-1: task: 4 kraj
pool-1-thread-2: task: 6 početak
pool-1-thread-1: task: 7 početak
pool-1-thread-2: task: 6 kraj
pool-1-thread-2: task: 8 početak
pool-1-thread-1: task: 7 kraj
pool-1-thread-1: task: 9 početak
pool-1-thread-2: task: 8 kraj
pool-1-thread-1: task: 9 kraj
Iz ispisa vidimo da su samo dvije niti izvršile sve poslove. Sljedeći primjer pokazuje
korištenje bazena s varijabilnim brojem niti.
public class CachedExecutorMain
{

public static void main( String[] args )


{
ExecutorService pool = Executors.newCachedThreadPool();
for ( int i = 0; i < 10; i++ )
{
pool.execute( new MyTask(i) );
}
}
}
Razlika u odnosu na prošli primjer je to što se na drugačiji način stvara bazen niti.
Ispis je sljedeći.
pool-1-thread-1: task: 0 početak
pool-1-thread-3: task: 2 početak
pool-1-thread-4: task: 3 početak
pool-1-thread-2: task: 1 početak
pool-1-thread-6: task: 5 početak
pool-1-thread-5: task: 4 početak
pool-1-thread-7: task: 6 početak
pool-1-thread-8: task: 7 početak
pool-1-thread-9: task: 8 početak
pool-1-thread-10: task: 9 početak
pool-1-thread-1: task: 0 kraj
pool-1-thread-3: task: 2 kraj
pool-1-thread-2: task: 1 kraj
pool-1-thread-4: task: 3 kraj
pool-1-thread-6: task: 5 kraj
pool-1-thread-5: task: 4 kraj
pool-1-thread-7: task: 6 kraj
pool-1-thread-8: task: 7 kraj
pool-1-thread-10: task: 9 kraj
pool-1-thread-9: task: 8 kraj
Iz ispisa vidimo da je pokrenuto 10 niti i svaka nit je dobila svoj posao. Sljedeći
primjer pokazuje kako se koristi složenije strategija.
public class ThreadPoolMain
{
public static void main( String[] args )
Konkurentnost u Javi 103

{
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 10,
10, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3));
BlockingQueue<Runnable> queue = pool.getQueue();

for ( int i = 0; i < 10; i++ )


{
pool.execute( new MyTask(i) );
System.out.println("stavljen task " + i);
System.out.println("POOL: ActiveCount=" +
pool.getActiveCount() + " queueSize=" + queue.size());
Thread.yield();
}

while(true) {
System.out.println("POOL: ActiveCount=" +
pool.getActiveCount() + " queueSize=" + queue.size());
try
{
Thread.sleep(500);
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
}
}
}
Na početku se stvara bazen niti stvaranjem objekta iz klase ThreadPoolExecutor.
Konstruktor koji je korišten ima 5 parametara:
1. minimalan (osnovni) broj niti koje će biti kreirane na početku,
2. maksimalan broj niti koji je će biti kreiran,
3. vrijeme koje je potrebno proći da se neka nit uništi ako se ne koristi,
4. vremenske jedinice za prethodni parametar i
5. rep u koji će se spremati poslovi dok čekaju na izvršavanje.
Nakon stvaranja bazena niti u petlji se stvara 10 poslova i daje bazenu na
izvršavanje. U petlji ispod se ispisuje koliko ima aktivnih niti i koliko je poslova u repu.
Ispis izvršavanja je sljedeći.
stavljen task 0
pool-1-thread-1: task: 0 početak
POOL: ActiveCount=1 queueSize=0
stavljen task 1
POOL: ActiveCount=1 queueSize=0
pool-1-thread-2: task: 1 početak
stavljen task 2
POOL: ActiveCount=2 queueSize=1
stavljen task 3
POOL: ActiveCount=2 queueSize=2
stavljen task 4
POOL: ActiveCount=2 queueSize=3
stavljen task 5
POOL: ActiveCount=2 queueSize=3
Konkurentnost u Javi 104

pool-1-thread-3: task: 2 početak


stavljen task 6
POOL: ActiveCount=3 queueSize=3
pool-1-thread-4: task: 3 početak
stavljen task 7
POOL: ActiveCount=4 queueSize=3
pool-1-thread-5: task: 4 početak
stavljen task 8
POOL: ActiveCount=5 queueSize=3
pool-1-thread-6: task: 5 početak
stavljen task 9
POOL: ActiveCount=6 queueSize=3
pool-1-thread-7: task: 6 početak
POOL: ActiveCount=7 queueSize=3
POOL: ActiveCount=7 queueSize=3
POOL: ActiveCount=7 queueSize=3
POOL: ActiveCount=7 queueSize=3
pool-1-thread-1: task: 0 kraj
pool-1-thread-1: task: 7 početak
pool-1-thread-2: task: 1 kraj
pool-1-thread-2: task: 8 početak
pool-1-thread-3: task: 2 kraj
pool-1-thread-3: task: 9 početak
pool-1-thread-4: task: 3 kraj
pool-1-thread-5: task: 4 kraj
pool-1-thread-6: task: 5 kraj
pool-1-thread-7: task: 6 kraj
POOL: ActiveCount=3 queueSize=0
POOL: ActiveCount=3 queueSize=0
POOL: ActiveCount=3 queueSize=0
POOL: ActiveCount=3 queueSize=0
pool-1-thread-1: task: 7 kraj
pool-1-thread-2: task: 8 kraj
pool-1-thread-3: task: 9 kraj
POOL: ActiveCount=0 queueSize=0
POOL: ActiveCount=0 queueSize=0
Iz ispisa se može zaključiti sljedeće. Nakon stavljanja prva dva posla na izvršavanje
oni se i pokreću. Nakon toga se ostali poslovi stavljaju u rep. Kada se rep napuni
onda se počnu stvarati nove niti koje preuzimaju poslove iz repa. Ukoliko bi bilo toliko
poslova da se ne može stvarati više niti onda bi došlo do bacanja iznimke prilikom
stavljanja.
Posao koji vraća rezultat mora implementirati sučelje Callable. To sučelje ima samo
jednu metodu (call) koja vraća rezultat. Prilikom slanja posla na obradu u bazen
potrebno je koristiti metodu submit. Ona stavlja posao na obradu i vraća objekt koji
implementira sučelje Future. Sučelje Future ima metode: za provjeru gotovosti
poslova (isDone), za prekidanje posla (cancel) i za dohvaćanje rezultata (get).
Kada se pozove metoda get onda se nit koje ju je pozvala blokira ako posao još
nije gotov. Imamo dvije verzije metode get: bez parametara koja čeka rezultat do
beskonačnosti i s parametrom koji definira koliko će se najduže čekati rezultat. Slijedi
primjer posla.
public class CallableTask
implements Callable<Integer>
{
private int data;
Konkurentnost u Javi 105

public CallableTask( int number )


{
data = number;
}

public Integer call() throws Exception


{
try
{
Thread.sleep( 150 );
}
catch ( Exception e )
{
e.printStackTrace();
}
return data*data;
}
}
Pokretanje poslova se radi na sljedeći način.
package hr.fer.tel.kp.executor;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableMain


{
public static void main( String[] args )
{
ExecutorService pool = Executors.newFixedThreadPool( 4 );
List<Future<Integer>> list =
new LinkedList<Future<Integer>>();

for ( int i = 0; i < 10; i++ )


{
list.add( pool.submit( new CallableTask( i ) ) );
}

for(Future<Integer> future: list) {


while(!future.isDone()) {
System.out.println("čekam future");
try
{
Thread.sleep(100);
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
}
Konkurentnost u Javi 106

try
{
System.out.println("rezultat=" + future.get());
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
catch ( ExecutionException e )
{
e.printStackTrace();
}
}
}
}
Rezultat izvršavanja je sljedeći.
čekam future
rezultat=0
rezultat=1
rezultat=4
rezultat=9
čekam future
čekam future
rezultat=16
rezultat=25
rezultat=36
rezultat=49
čekam future
rezultat=64
rezultat=81

1.36 Primijenjena konkurentnost


Praktična primjena konkurentnosti je u grafičkim sustavima, raspodijeljenim sustavima,
mrežnom programiranju, agentskim sustavima, itd. U ovom poglavlju ćemo vidjeti
nekoliko primjera primijenjene konkurentnosti.

1.36.1 Grafika i niti


U Javi se grafički sustav zove Swing. On je u potpunosti implementiran u Javi i zbog
toga je potpuno prenosiv, ali na svakom operacijskom sustavu ne izgleda potpuno isto
kao aplikacije koje su napravljene za taj sustav. Swing komponente su napravljene
tako da se pristup i mijenjanje podataka u komponentama vrši kroz nit koja je
zadužena za distribuciju događaja (event dispatch thread). Ta nit distribuira događaje
(pomak miša, klik, pritisak tipke, iscrtavanje prozora, itd.) tako da poziva callback
metode nekog sučelja (npr. actionPerformed za pritisak na tipku miša). Pošto je jedna
nit zadužena za sve događaje obrada događaja ne smije biti dugačka jer se blokira
ponovno iscrtavanje. Ukoliko obrada događaja traje dugo onda je potrebno
pokrenuti novu nit koja će vršiti obradu. Problem koji nastaje je da sada imamo više
niti u sustavu i ako iz niti, koja je pokrenuta da se ne blokira iscrtavanje, treba
mijenjati podatke u grafičkim komponentama onda je potrebno sve to sinkronizirati.
Jedan oda načina kako se to može napraviti (tvorci Jave preporučaju njegovo
korištenje) je pomoću poziva metoda:
Konkurentnost u Javi 107

• public static void invokeLater(Runnable doRun) ili


• public static void invokeAndWait(Runnable doRun) throws
InterruptedException, InvocationTargetException
Ove metode se nalaze u klasi SwingUtilities, a objekt Runnable vrši obradu
koju poziva nit event dispatch. Metoda invokeLater objekt doRun stavlja u rep
poslova koje treba obratiti nit event dispatch i odmah se vraća tako da se izvršavanje
može nastaviti. Za razliku od nje, metoda invokeAndWait radi istu stvar samo što
se ne vraća izvršavanje iz nje dok nit event dispatch ne obradi taj posao. Tek nakon
obrade posla izvođenje se nastavlja.
Prvo pogledajmo primjer neispravne obrade događaja.
public class WrongWay
extends JFrame
{
JButton btnAction = new JButton();
JProgressBar pbProgress = new JProgressBar();

public WrongWay()
{
pbProgress.setMaximum( 10 );
pbProgress.setMinimum( 0 );
try
{
jbInit();
}
catch ( Exception e )
{
e.printStackTrace();
}
}

private void jbInit() throws Exception


{
btnAction.setText( "Start" );
btnAction.addActionListener(
new java.awt.event.ActionListener()
{
public void actionPerformed( ActionEvent e )
{
btnAction_actionPerformed( e );
}
} );
this.getContentPane().add( btnAction, BorderLayout.WEST );
this.getContentPane().add( pbProgress,
BorderLayout.CENTER );
}

void btnAction_actionPerformed( ActionEvent e )


{
pbProgress.setValue( 0 );
for ( int i = 0; i <= 10; i++ )
{
pbProgress.setValue( i );
try
{
Konkurentnost u Javi 108

Thread.sleep( 500 );
}
catch ( InterruptedException ie )
{
}
}
}

public static void main( String[] args )


{
WrongWay wrongWay1 = new WrongWay();
wrongWay1.pack();
wrongWay1.setVisible( true );
}
}
U ovom primjeru imamo klasu WrongWay koja u grafičkom sučelju ima jednu tipku
(btnAction) i jedan grafički element koji prikazuje završenost posla
(JProgressBar). Kada se pritisne tipka pozove se metoda
btnAction_actionPerformed koja u petlji postavlja vrijednost završenosti posla.
Između svakog postavljanja se pričeka 500 ms tako da se pozove metoda sleep.
Kako je nit koja izvršava tu petlju zadužena i za iscrtavanje prozora u periodu dok
petlja ne završi kompletno grafičko sučelje se blokira. Da bismo to riješili potrebno je
napraviti novu nit koja će izvršavati petlju. Na taj način ćemo osloboditi nit za
iscrtavanje. Sljedeći primjer pokazuje upravo to.
public class CorrectWay
extends JFrame
{
JButton btnAction = new JButton();

JProgressBar pbProgress = new JProgressBar();

public CorrectWay()
{
pbProgress.setMaximum( 10 );
pbProgress.setMinimum( 0 );
try
{
jbInit();
}
catch ( Exception e )
{
e.printStackTrace();
}
}

private void jbInit() throws Exception


{
btnAction.setText( "Start" );
btnAction.addActionListener( new
java.awt.event.ActionListener()
{
public void actionPerformed( ActionEvent e )
{
btnAction_actionPerformed( e );
Konkurentnost u Javi 109

}
} );
this.getContentPane().add( btnAction, BorderLayout.WEST );
this.getContentPane().add( pbProgress, BorderLayout.CENTER
);
}

private void btnAction_actionPerformed( ActionEvent e )


{
btnAction.setEnabled( false );
new CountThread( this ).start();
}

public void setProgressBarValue( int value )


{
pbProgress.setValue( value );
}

public void setEnabledStartButton( boolean enable )


{
btnAction.setEnabled( enable );
}

public static void main( String[] args )


{
CorrectWay correctWay1 = new CorrectWay();
correctWay1.pack();
correctWay1.setVisible( true );
}
}
Nova nit koja izvršava petlju je stavljna u klasu CountThread. Da bi ta nit mogla
postavljati vrijednost gotovosti potrebno je bilo napraviti i metodu
setProgressBarValue.
public class CountThread
extends Thread
{
private CorrectWay gui;

public CountThread( CorrectWay gui )


{
super();
this.gui = gui;
}

public void run()


{
try
{
SwingUtilities.invokeAndWait( new SetValue( 0, gui ) );
}
catch ( Exception e )
{
}
Konkurentnost u Javi 110

gui.setProgressBarValue( 0 );
for ( int i = 0; i <= 10; i++ )
{
SwingUtilities.invokeLater( new SetValue( i, gui ) );
try
{
Thread.sleep( 500 );
}
catch ( InterruptedException ie )
{
}
}

SwingUtilities.invokeLater( new Runnable()


{
public void run()
{
gui.setEnabledStartButton( true );
}
} );
}
}
Nit CountThread na početku izvršavanja postavlja vrijednost gotovosti na 0 tako
da pozove metodu inokeAndWait kojoj je parametar objekt iz klase SetValue, a
implementira sučelje Runnable. Nakon toga se ulazi u petlju u kojoj se nakon svakih
500 ms pomoći poziva metode invokeLater postavljaju nove vrijednosti gotovosti. Na
kraju se još omogućava ponovni pritisak na gumb (Runnable koji poziva metodu
setEnableStartButton). Pogledajmo još klasu SetValue.
public class SetValue
implements Runnable
{
private int value;

private CorrectWay gui;

public SetValue( int i, CorrectWay gui )


{
value = i;
this.gui = gui;
}

public void run()


{
gui.setProgressBarValue( value );
}
}

1.36.2 Programiranje mrežnog poslužitelja


Uzmimo za primjer jednog klijenta i poslužitelja koji koristi TCP komunikaciju. Klijent se
spaja na poslužitelj na vrata (port) 4444 i nakon spajanja dobije pozdravnu poruku
od poslužitelja. U klijentu korisnik može poslati poruku na poslužitelj koju mu
poslužitelj vrati u odgovoru (primjer jeke). Ako klijent pošalje poruku „bok“ poslužitelj
ga odzdravlja i zatvara konekciju. Pogledajmo prvo klijenta.
Konkurentnost u Javi 111

public class Client


{
public static void main( String[] args ) throws Exception
{
Socket scEcho = new Socket( "localhost", 4444 );
PrintWriter pwOut =
new PrintWriter( scEcho.getOutputStream(), true );
BufferedReader brIn =
new BufferedReader( new InputStreamReader(
scEcho.getInputStream() ) );

BufferedReader brStdIn =
new BufferedReader( new InputStreamReader(
System.in ) );
String strUserInput;

System.out.println( "echo:" + brIn.readLine() );


while ( ( strUserInput = brStdIn.readLine() ) != null )
{
pwOut.println( strUserInput );
String strFromServer = brIn.readLine();
System.out.println( "echo: " + strFromServer );
if ( strFromServer.endsWith( "Ajde bok!" ) )
break;
}
pwOut.close();
brIn.close();
scEcho.close();
System.out.println( "Kraj!" );
}
}
Klijentski kod stvara objekt Socket koji se spaja na lokalno računalo na vrata 4444.
Nakon toga stvara ulazne i izlazne tokove. U kodu se prvo pročita jedan redak s
poslužitelja koji se ispiše. Nakon toga se ulazi u petlju u kojoj se čita redak koji je
korisnik natipkao i taj redak se šalje na poslužitelj. Nakon toga se čeka odgovor
poslužitelja koji se ispiše. Ako je poslužitelj odgovorio s odzravnom porukom onda se
izlazi iz petlje i zatvaraju svi tokovi. Pogledajmo sada posljužitelja.
public class Server
{
public static void main( String[] args ) throws Exception
{
ServerSocket ssc = new ServerSocket( 4444 );
while ( true )
{
Socket scClient = ssc.accept();
ServerThread st = new ServerThread( scClient );
st.start();
}
}
}
Poslužiteljski kod stvara objekt ServerSocket koji sluša na zadanim vratima. U
beskonačnoj petlji se prvo pozove metoda accept koja čeka da se klijent spoji.
Kada se klijent spoji izlazi se is te metode i pokreće nova nit (ServerThread) koja
će obrađivati spojenog klijenta. Nakon pokretanja niti glavna nit ponovno poziva
Konkurentnost u Javi 112

metodu accept i čeka sljedećeg klijenta. Na taj način je omogućena konkurentna


obrada klijenata. Pogledajmo što radi poslužiteljska nit za obradu klijenata.
public class ServerThread
extends Thread
{
private Socket socket;

public ServerThread( Socket socket )


{
this.socket = socket;
}

public void run()


{
try
{
PrintWriter out =
new PrintWriter( socket.getOutputStream(), true );
BufferedReader in =
new BufferedReader( new InputStreamReader( socket
.getInputStream() ) );
String strInLine, strOutLine;

strOutLine = processInput( null );


out.println( strOutLine );
while ( ( strInLine = in.readLine() ) != null )
{
strOutLine = processInput( strInLine );
out.println( strOutLine );
if ( strOutLine.equals( "Ajde bok!" ) )
{
break;
}
}

out.close();
in.close();
}
catch ( IOException ioe )
{
}
finally
{
try
{
socket.close();
}
catch ( Exception e )
{
}
}
}

public String processInput( String input )


{
Konkurentnost u Javi 113

if ( input == null )
{
return "Bok!";
}

if ( input.equalsIgnoreCase( "Bok" ) )
{
return "Ajde bok!";

}
else
{
return input;
}
}
}
U poslužiteljskoj niti se prvo stvaraju tokovi podataka prema klijentu. Nakon toga se
šalje pozdravna poruka te se ulazi u prelju u kojoj se čeka korisnikov „zahtjev“ koji se
vraća kako odgovor. Ako je korisnik poslao „bok“ onda mu se odgovori s „Ajde bok!“
i izlazi se iz petlje. Nakon toga se zatvaraju tokovi i konekcija.
U ovom primjeru poslužitelj može posluživati velik broj korisnika tj. onoliko korisnika
koliko mu resursi dozvoljavaju. U slučaju da je broj korisnika jako velik i da ih
poslužitelj ne može efikasno opsluživati sve se usporava do trenutka ispada
poslužitelja. Kako bi se to spriječilo potrebno je ograničiti broj korisnika koji se mogu
istodobno obrađivati. To možemo napraviti pomoću bazena niti. Pogledajmo primjer
poslužitelja koji koristi bazen niti.
public class PoolServer
{
public static void main( String[] args ) throws Exception
{
ServerSocket ssc = new ServerSocket( 4444 );
ExecutorService pool = Executors.newFixedThreadPool( 2 );

while ( true )
{
Socket scClient = ssc.accept();
ServerExecutor st = new ServerExecutor( scClient );
pool.execute( st );
}
}
}
Ovaj poslužitelj stvara ExecutorService koji ima dvije niti u bazenu i u petlji šalje
na obradu objekt ServerExecutor u kojem je implementacija obrade klijenta.
Pogledajmo kako se obrađuje klijent.
public class ServerExecutor
implements Runnable
{
Socket socket;

public ServerExecutor( Socket socket )


{
this.socket = socket;
}
Konkurentnost u Javi 114

public void run()


{
try
{
PrintWriter out =
new PrintWriter( socket.getOutputStream(), true );
BufferedReader in =
new BufferedReader( new InputStreamReader( socket
.getInputStream() ) );
String strInLine, strOutLine;

strOutLine = Thread.currentThread().getName() + ": "


+ processInput( null );
out.println( strOutLine );
while ( ( strInLine = in.readLine() ) != null )
{
strOutLine = Thread.currentThread().getName() + ": "
+ processInput( strInLine );
out.println( strOutLine );
if ( strInLine.equalsIgnoreCase( "Ajde bok!" ) )
{
break;
}
}

out.close();
in.close();
}
catch ( IOException ioe )
{
}
finally
{
try
{
socket.close();
}
catch ( Exception e )
{
}
}
}

public String processInput( String input )


{
if ( input == null )
{
return "Bok!";
}

if ( input.equalsIgnoreCase( "Bok" ) )
{
return "Ajde bok!";

}
Konkurentnost u Javi 115

else
{
return input;
}
}
}
Kako što vidimo iz koda ServerThread i ServerExecutor se razlikuju samo u
tome što je ServerThread nit, a ServerExecutor implementira sučelje
Runnable. Sve ostalo je isto.
Kada se više od dva klijenta spajaju na poslužitelj onda se oni stavljaju u rep i kada
se neka nit oslobodi onda se sljedeći klijent obrađuje.
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 116

1. POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG


Povezivanje dva programa koji su napisani u dva programska jezika obično se radi
na jedan od sljedećih načina:
• povezivanjem pomoću procesa,
• povezivanje pomoću mreže korištanjem protokola TCP ili UDP,
• povezivanje pomoću CORBA-e ili
• neki drugi mehanizam ovisan o programskom jeziku (npr. JNI – Java Native
Interface, Jinterface – Java Interface).
Povezivanje Jave i Erlanga moguće je na sljedeće načine:
• povezivanje pomoću procesa ili portsa,
• povezivanje pomoću mreže ili
• povezivanje pomoću Jinterfacea.
Što se tiče smjera povezivanja postoji dva smjera i shodno tome određeni mehanizmi:
• Java poziva Erlang i
o povezivanje pomoću procesa,
o pozivanje pomoću mreže (TCP ili UDP),
o povezivanje pomoću Jinterfacea,
• Erlang poziva Javu,
o povezivanje pomoću procesa,
o povezivanje pomoću portsa,
o povezivanje pomoću mreže (TCP ili UDP),
o povezivanje pomoći Jinterfacea.

1.1. Povezivanje pomoću procesa


Povezivanje programa pomoću procesa je moguće izmeđi bilo koja dva programska
jezika. Bitno je da jedan program pokreće drugi i da s njim komunicira pomoću
ulaznih i izlaznih tokova podataka. Tipičan primjer takve komunikacije je povezivanje
procesa na operacijskim sustavima koji koriste mehanizme iz UNIX-a. Primjer:

> cat neki.txt | tail

U ovom primjeru smo pokrenuli program cat koji ispisuje na standardni izlaz sadržaj
datoteke neki.txt. Izlaz tog programa je spojen na ulaz programa tail koji
ispisuje zadnjih nekoliko redaka svog ulaza. Na taj način su oca dva procesa
povezana. Ovdje nije bitno u kojem su programskom jeziku ovi programi napisani. Istu
stvar može se napraviti i programski.
Da bi dva programa mogla komunicirati na ovaj način potrebno je definirati protokol
kojim komuniciraju. U navedenom primjeru je protokol vrlo jednostavan. Oba
programa barataju s tekstom tj. program cat ispisuje tekst na standardni izlaz, a
program tail prime tekst sa standardnog ulaza.

1.1.1. Ports
Ports je mehanizam koji je ugrađen Erlang i služi za jednostavniju komunikaciju između
procesa u Erlangu koji pokreće neki drugi proces tj. u našem slučaju proces koji
izvršava program u Javi.
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 117

Za stvaranje porta koristi se sljedeća naredba u Erlangu:


Port = open_port(ImePorta, PostavkePorta)

Rezultat izvođenja je PID procesa koji je povezan s portom. Na taj PID može se
poslati sljedeće:
Port ! {MojPID, {command, Podaci}}
Ovdje se šalje poruka s podacima na pokrenuti proces. Prvi parametar (MojPID) je
PID procesa koji će primiti resultat, drugi je n-torka gdje je prvi parametar atom koji
označava što se šalje portu. U ovom slučaju je to atom command koji označava de se
procesu šalje naredba. Drugi element n-torke (Podaci) je podatak koji se šalje
procesu.
Sljedeća naredba:
Port ! {MojPID, close}
Zatvara komunikaciju s portom i gasi pokrenuti proces.
Kade se želi primiti odgovor od preko porta onda se koristi sljedeći odlomak:
receive
{Port, {data, Podaci1}} ->
% obrada podataka

Pogledajmo sada koji su parametri pokretanja procesa pomoću porta.
Port = open_port({spawn, "./program"}, [{packet, 2}])
Prvi parametar je n-torka čiji je prvi element atom koji označava naredbu za
pokretanje. U ovom primjeru koristimo atom spawn koji označava da pokrećemo novi
proces. Za ostale naredbe pogledajte u Erlangovu domumentaciju. Drugi element n-
torke je tekst tj. lista brojeva koje predstavlja naredbu koja pokreće taj proces. U
ovom primjeru je to pokretanje programa program koji se nalazi u trenutnom
direktoriju. Drugi parametar funkcije open_port je lista koja ima jedan element, a
to je n-torka. Ta n-torka ima za prvi element atom packet koji označava da će
komunikacija biti pomoću paketa, a drugi element označava broj koji predstavlja
koliko prvih okteta svakog paketa će prikazivati duljinu svakog paketa. U ovom
slučaju je to 2 okteta za svaki paket. Primjer: ako šaljemo ovu listu "Bok!" pomoću
naredbe
Port ! {MojPID, {command, "Bok!"}}
Stvarni podaci koji će se poslati preko porta na proces je sljedeća lista [0, 4, 66,
111, 107, 33].

Pogledajmo sada primjer progama u Javi i Erlangu koji komuniciraju. Program u


Erlangu poziva dvije funkcije (plus i minus) u Javi koja prime podatke i vraća rezultat.
-module(my_ports).
-export([start/0, stop/0]).
-export([plus/2, minus/2]).

start() ->
spawn(fun() ->
register(my_ports, self()),
process_flag(trap_exit, true),
Port = open_port({spawn,
"java -cp classes hr.fer.tel.kp.erlang.ports.JavaPort"},
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 118

[{packet, 2}]),
loop(Port)
end).

stop() ->
my_ports ! stop.

plus(X,Y) -> call_port({plus, X, Y}).

minus(X,Y) -> call_port({minus, X, Y}).

call_port(Msg) ->
my_ports ! {call, self(), Msg},
receive
{my_ports, Result} ->
Result
end.

loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, encode(Msg)}},
receive
{Port, {data, Data}} ->
Caller ! {my_ports, decode(Data)}
end,

loop(Port);
stop ->
Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;

{'EXIT', Port, Reason} ->


exit({port_terminated,Reason})
end.

encode({plus, X, Y}) -> [0, X, Y];


encode({minus, X, Y}) -> [1, X, Y].

decode([Int]) -> Int.

U funkciji start pokreće se novi proces koji se registrira pod imenom my_ports i
pokreće program u Javi. Nakon toga se poziva funkcija loop. Funkcija loop prima
naredbe od drugih procesa i šalje poruke portu te prima odgovor. Kada primi n-
torku {call, Caller, Msg} poruku u varijabli Msg kodira i šalje na port.
Varijabla Msg može biti oblika {plus, X, Y} ili {minus, X, Y}. Takve poruke
se kodiraju u funkciji encode. Prvi element n-torke predstavlja koja se funkcija u Javi
treba pozvati, a X i Y su parametri. Funkcija plus se kodira u broj 0, a funkcija minus u
broj 1. Nakon slanja poruke na port, proces čeka rezultat u sljedećem obliku {Port,
{data, Data}}. Kada se primi ta poruka onda se odgovor šalje procesu koji
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 119

poslao prvu poruku procesu my_ports, a to je proces koji je poslan kako parametra
Caller. Tipičan primjer korištenja je sljedeći:
> my_ports:start().
> my_ports:plus(2,3).
> my_ports:minus(5,1).
> my_ports:stop().
Ovdje prvo pokrećemo program u Javi pozivom funkcije start. Nakon toga
pozivamo funkciju plus ili minus i na kraju pozivom funkcije stop zaustavljamo
izvođenje programa u Javi.
Progarm u Javi pokreće se u klasi JavaPort.
public class JavaPort {
public static void main( String[] args ) {
Communication com = new Communication();
com.start();
}
}

Ovdje se stvara objekt Communication koji je zadužen za komunikaciju i poziva se


njegova metoda start koja pokreće komunikaciju.
public class Communication {
private InputStream in;
private OutputStream out;
private boolean closeConnection;

public Communication() {
in = System.in;
out = System.out;
closeConnection = false;
}

public Communication( InputStream in, OutputStream out,


boolean closeConnection )
{
this.in = in;
this.out = out;
this.closeConnection = closeConnection;
}

public void start() {


loop();
}

private void loop() {


boolean end = false;

while(!end) {
try {
Command command = readCommand();
command.execute( this );
if(closeConnection)
end = true;
} catch (Exception e) {
e.printStackTrace();
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 120

end = true;
}
}
}

private Command readCommand() throws IOException {


int length = readLength();
//System.err.println("length = " + length);
if(length == -1)
System.exit( 0 );

byte buffer[] = readBuffer(length);

return decodeBuffer(buffer);
}

private Command decodeBuffer( byte[] buffer )


{
//System.err.println("in decodeBuffer: buffer[0]=" +
// buffer[0]);
switch(buffer[0]) {
case 0:
return decodeCommandPlus(buffer);
case 2:
return decodeCommandMinus(buffer);
default:
throw new RuntimeException("Illegal command code: " +
buffer[0]);
}
}

private Command decodeCommandMinus( byte[] buffer ) {


byte num1 = buffer[1];
byte num2 = buffer[2];

return new CommandMinus(num1, num2);


}

private Command decodeCommandPlus( byte[] buffer ) {


byte num1 = buffer[1];
byte num2 = buffer[2];

return new CommandPlus(num1, num2);


}

private byte[] readBuffer( int length ) throws IOException {


//System.err.println("in readBuffer: length = " + length);
byte buffer[] = new byte[length];

in.read( buffer );

//System.err.println("in readBuffer: after read");

return buffer;
}
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 121

private int readLength() throws IOException {


int length = in.read();
if(length == -1)
return -1;

int length2 = in.read();


if(length2 == -1)
throw new EOFException();

return length*256 + length2;


}

public void writeByte( byte result ) throws IOException {


out.write( 0 );
out.write( 1 );
out.write( result );
out.flush();
}
}

Kada se stvori klasa pomoću konstruktora bez parametara onda se komunikacija


veže na standardne tokove podataka, a inače se veže na zadane tokove.
Metoda start poziva metodu loop koja pokreće petlju u kojoj se čita paket po
paket i izvršava naredba iz svakog paketa te vraća rezultat. Čitanje jedne naredbe
vrši se u metodi readCommand koja prvo pročita prva dva okteta koji predstavljaju
duljinu paketa i nakon toga se čita toliko okteta koliko je pročitano da je duljina
paketa. To se stavlja u polje okteta. Nakon toga se to polje dekodira u metodi
decodeBuffer kojia vraća objekt Command. Objekt Command predstavlja jednu
naredbu koja se može izvršiti.
public interface Command {
void execute(Communication comunication) throws IOException;
}

public class CommandMinus implements Command {


private byte num1, num2;

public CommandMinus( byte num1, byte num2 ) {


this.num1 = num1;
this.num2 = num2;
}

public void execute( Communication communication )


throws IOException
{
byte result = (byte)(num1 - num2);
communication.writeByte(result);
}
}

public class CommandPlus implements Command {


private byte num1, num2;

public CommandPlus( byte num1, byte num2 ) {


this.num1 = num1;
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 122

this.num2 = num2;
}

public void execute( Communication communication )


throws IOException
{
byte result = (byte)(num1 + num2);
communication.writeByte(result);
}
}

Kada se pozove metoda execute u objektu command onda se izvrši funkcija


zbrajanja ili oduzimanja ovisi o objektu. Nakon izvršavanja funkcije rezultat se vraća
natrag Erlangu pozivom metode writeByte koja kodira jedan oktet u tri (prva dva
da duljinu paketa i taj sami oktet).

1.2. Povezivanje pomoću mreže


Povezivanje Java i Erlang pomoću mreže ukoliko se koristi protokol TCP ne razlikuje
se puno od komunikacije pomoću porta. Razlika je jedino u tome što u Erlangu nema
programsko sučelja koje olakšava kodiranje paketa kako kod porta, već se taj dio
mora napisati. Pogledajmo kako izgleda kod u Erlangu.
-module(my_tcp).
-export([start/0, stop/0]).
-export([plus/2, minus/2, decode/1]).
-import(lists, [reverse/1]).

start() ->
spawn(
fun() ->
register(my_tcp, self()),
process_flag(trap_exit, true),
loop()
end).

stop() ->
my_tcp ! stop.

plus(X,Y) -> call_tcp({plus, X, Y}).

minus(X,Y) -> call_tcp({minus, X, Y}).

call_tcp(Msg) ->
my_tcp ! {call, self(), Msg},
receive
{my_tcp, Result} ->
Result
end.

loop() ->
receive
{call, Caller, Msg} ->
{ok,Socket} = gen_tcp:connect("localhost",6000,
[binary, {packet, 0}]),
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 123

ok = gen_tcp:send(Socket, encode(Msg)),
Data = receive_data(Socket, []),
%io:format("~p~n", [Data]),
Caller ! {my_tcp, decode(Data)},
loop();
stop ->
exit(normal)
end.

receive_data(Socket, SoFar) ->


receive
{tcp,Socket,Bin} ->
receive_data(Socket, [Bin|SoFar]);
{tcp_closed,Socket} ->
binary_to_list(list_to_binary(reverse(SoFar)))
end.

encode({plus, X, Y}) -> [0, 3, 0, X, Y];


encode({minus, X, Y}) -> [0, 3, 1, X, Y].

decode([_,_,Int]) -> Int.

Razlika između ovog programa i programa koji komunicira pomoću porta je u tome
što ovdje imamo funkcije kodiranja promijenjene tako da dodaju na početak veličinu
paketa i što se u funkciji loop koristi komunikacija protokolom TCP. Otvaranje
konekcije vrši se funkcijom gen_tcp:connect koja otvara konekciju na lokalno
računalo na vrata 6000. Konekcija je binarna i paketska. Za dodatne parametre
pogledajte Erlangovu dokumentaciju. Rezultat izvođenja te funkcije je n-torka koja za
uspješno otvaranje konekcija vraća rezultat u sljedećem obliku {ok,Socket}.
Varijabla Socket sadrži identifikator konekcije. Slanje paketa preko mreže vrši se
funkcijom gen_tcp:send koja kako prvi parametar prima identifikator konekcije, a
drugi parametar je lista brojeva (podaci). Primanje podataka vrši se u funkciji
receive_data koja rekurzivno prima pakete. Kada je konekcija zatvorena onda će
se primiti poruka u sljedećem obliku {tcp_closed,Socket}. Nakon toga se
primljeni podaci dekodiraju u funkciji decode i vraćaju pozivatelju.
Dio koda u Javi pokreće se odvojeno prije pokretanja u Erlangu.
public class ErlangTcp {
public static void main( String[] args ) throws Exception {
ServerSocket serverSocket = new ServerSocket(6000);
while(true) {
Socket socket = serverSocket.accept();

Communication communication = new Communication(


socket.getInputStream(),
socket.getOutputStream(),
true);
communication.start();
socket.close();
}
}
}
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 124

U Javi se prvo stavra objekt ServerSocket koji sluša na vratima 6000. Nakon toga se
okreće čekanje klijenata pozivom metode accept. Nakon toga se stvara objekt
Connection koji služi za komunikaciju kao u kod korištenje portova. Nakon završetka
obrade jednog zahtjeva ponovno se pokreće čekanje klijenata.
Tipičan primjer korištenja je sljedeći (slično kako kod portova):
> my_tcp:start().
> my_tcp:plus(2,3).
> my_tcp:minus(5,1).
> my_tcp:stop().

1.3. Povezivanje pomoću biblioteke Jinterface


Paket Jinterface je paket u Javi koji se koristi za komunikaciju između Jave i Erlanga.
Da bi se taj paket mogao koristiti potrebno je u projekt ukljućiti datoteku
OtpErlang.jar koja se nalazi u direktoriju erlang/lib/jinterface-
1.4.2/priv/. Direktorij erlang je direktorij gdje je instaliran Erlang.
U paket su ugrađeni mehanizmi za komunikaciju s Erlangom. On omogućuje
pokretanje procesa u Javi koji Erlang vidi kako normalni Erlangov proces. To je
realizirano klasom com.ericsson.otp.erlang.OtpMbox. Kako bi komunikacija
bila što jednostavnija u Javi su napravljene klase koje predstavljaju sve vrste
podataka koje se koriste u Erlangu. Erlangove vrste podataka su u javi
implementirane u posklasama klase OtpErlangObject. Tablica 1.1 prikazuje
preslikvanje Erlangovih i Javinih podataka. Posebna vrsta podataka su atomi true i
false koji se preslikavanju u klasu OtpErlangBooelan. Isto tako je u Javi
definirana vrsta podataka OtpErlangString koja predstavlja listu brojeva koji
predstavljaju ispisive znakove.

Tablica 1.1 – Preslikavanje vrsta podataka između Jave i Erlanga

Erlangove vrste Javine vrste podataka


atom OtpErlangAtom
binary OtpErlangBinary
podaci s decimalnim OtpErlangFloator ili OtpErlangDouble
zarezom
cjelobrojni podaci OtpErlangByte, OtpErlangChar, OtpErlangShort,
OtpErlangUShort, OtpErlangInt, OtpErlangUInt
list OtpErlangList
pid OtpErlangPid
port OtpErlangPort
ref OtpErlangRef
tuple OtpErlangTuple
term OtpErlangObject

Klasa OtpNode predstavlja Erlangov čvor. Na jednom računalu se može nalaziti više
čvorova koji moraju imati različita imena. U sljedećoj naredbi se pokreće jedan takav
čvor, a na njegovo ime se nadodaje ime računala i vrata na kojima sluša poruke
(sustav ih definira ako nije eksplicitno navedeno).

OtpNode node = new OtpNode("javaNode");

Erlangovi procesi se razlikuju po identifikatorima (PID) ili po registriranim imenima


unutar svakog čvora. Svaki takav proces ima svoj sandučić s porukama. Jinterface ima
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 125

sličan mehanizam koji služi za slanje i primenje poruka. U Javi je sandučić


predstavljen klasom OtpMbox. Sljedeća naredba stvara sandučić unutar čvora.

OtpMbox mbox = node.createMbox();

Takav sandučić je moguće registrirati pod određenim imaenom pomoću sljedeće


naredbe.

mbox.registerNeme( "jMBox" );

Ako želimo ovo stvoriti sandučić i odmah ga registrirati pod određenim imenom onda
koristimo sljedeću naredbu.

OtpMbox mbox = node.createMbox( "jMBox" );

Pomoću metode self() u sandučiću dobije se PID trenutnog procesa tj. njegovog
sandučića. Slanje podataka vrši se pomoću metode send koja ima tri verzije:
• send(OtpErlangPid pid, OtpErlangObject podatak) – šalje
podatak na proces s zadanim PID-om,
• send(String registiranoIme, OtpErlangObject podatak) –
šalje podatak na proces s registriranim imenom i
• send(String registiranoIme, String cvor, OtpErlangObject
podatak) – šalje podatak na proces s registriranim imenom na zadanom
čvoru.
Poruke se šalju asinkrono tj. nakon slanja poruke program se nastavlja izvršavati. Da
bi se primio odgovor potrebno je pozvati metodu receive. Npr.

OtpErlangObject poruka = mbox.receive();

Progam se zaustavlja dok se u sandučiću ne nađe poruka. Ako je poruka već prije
stavljena u sandučić onda se ona uzima i program se nastavlja s izvođenjem.
Procesi se mogu povezati kao i u Erlangu. To se postiže metodama link i unlink.
U sljedećem kodu je primjer Erlangovog koda.

-module(my_ji).
-export([start/0, stop/0]).
-export([plus/2, minus/2]).

start() ->
spawn(
fun() ->
register(ePid, self()),
process_flag(trap_exit, true),
loop()
end).

stop() ->
ePid ! stop.

plus(X,Y) -> call_ji({plus, X, Y}).


POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 126

minus(X,Y) -> call_ji({minus, X, Y}).

call_ji(Msg) ->
ePid ! {call, self(), Msg},
receive
Result ->
Result
end.

loop() ->
receive
{call, Caller, Msg} ->
{jMBox, 'javaNode@localhost'} ! {self(), Msg},
receive
Result ->
%io:format("~p, ~p, ~p~n",
% [Result, self(), Caller]),
Caller ! Result
end,
loop();
stop ->
exit(normal)
end.

Kao što se vidi iz koda razlika je u slanju koje poruke kodu u Javi. Slanje se vrši kao i
slanje poruke bilo kojem drugom procesu:

{jMBox, 'javaNode@localhost'} ! {self(), Msg}

Primenje poruke je realizirano na jednaki način kao primenje poruke od nekog


drugog procesa:

receive
Result ->
% obrada poruke
end,

Program u Javi izgleda ovako:


public class JinterfaceServer {
public static void main( String[] args ) throws Exception {
OtpNode node = new OtpNode( "javaNode@localhost" );
node.setCookie( "kp" );
OtpMbox mbox = node.createMbox( "jMBox" );

while ( true ) {
OtpErlangObject obj = mbox.receive();

OtpErlangTuple tuple = (OtpErlangTuple) obj;


OtpErlangPid pid = (OtpErlangPid) tuple.elementAt( 0 );
OtpErlangTuple tuple2 =
(OtpErlangTuple) tuple.elementAt( 1 );
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 127

OtpErlangAtom function =
(OtpErlangAtom) tuple2.elementAt( 0 );
OtpErlangLong num1 =
(OtpErlangLong) tuple2.elementAt( 1 );
OtpErlangLong num2 =
(OtpErlangLong) tuple2.elementAt( 2 );

if ( function.equals( new OtpErlangAtom( "plus" ) ) ) {


mbox.send( pid, new OtpErlangLong( num1.byteValue() +
num2.byteValue() ) );
} else if (function.equals(
new OtpErlangAtom("minus") ))
{
mbox.send( pid, new OtpErlangLong( num1.byteValue() -
num2.byteValue() ) );
} else {
throw new RuntimeException("Illegal function");
}
}
}
}

U Javi se prvo stvara čvora s imenom javaNode@localhost. Zatim se postavlja


kolačić pomoću metode setCookie. To je potrebno da bi dva razpodijeljena čvora
mogla sigurno komunicirati. Nakon toga se stavara sandučić s imenom jMBox. Iza
toga se u petlji prima poruka koje se razlaže na elemente. Poruka je oblika {Pid,
Msg} gdje je Msg oblika {plus, X, Y} ili {minus, X, Y}. Nakon toga se na
osnovu primljene poruke izračunava rezultat i vraća nazad pozivom metode send.
Ukoliko je došlo do pogreške baca se iznimka za vrijeme rada.
Tipičan scenarij korištenja je da se prvo pokrene program u Javi, zatim se pokrene
Erlangov virtualni stroj sljedećom naredbom:

erl -sname x -setcookie kp

Nakon toga se mogu koristiti sljedeće komande u Erlangu:

> my_ji:start().
> my_ji:plus(2,3).
> my_ji:minus(5,1).
> my_ji:stop().
PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA PROGRAMA 128

2. PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA


PROGRAMA

2.1. Definicija modela


Za svaki skup poslova koji se rješava programski, može se procijeniti minimalan broj
procesora potreban da se skup poslova završi u najkraćem mogućem vremenu.
Početna je pretpostavka da se zna trajanje svakog posla ili se može procijeniti, te da
su poznati ulazni podaci potrebni za pokretanje posla i izlazni podaci koji su rezultat
obavljenog posla.

Ako se promatraju dva posla, oni se moraju izvoditi serijski (slijedno) ukoliko su izlazni
podaci jednoga ulazni podaci za drugoga. Ukupno trajanje jednako je zbroju
trajanja ta dva posla, a za izvođenje je dovoljan jedan procesor (sl. 3.1).

1
z1 z2
1 2 3 t
Slika 2.1 - Slijedno izvođenje poslova

Dva posla se mogu izvesti paralelno ako jedan drugome ne dostavljaju podatke, tj.
ako izlazni podaci od jednog nisu ulazni podaci za drugi. Takva dva posla su
konkurentna, tj. mogu se izvesti paralelno (istodobno). Pritom je najkraće vrijeme
izvođenja jednako duljem vremenu izvođenja posla, uz uvjet da se raspolaže s dva
procesora tako da istodobno izvode svaki svoj posao (sl. 3.2). Logika izvođenja je "I",
tj. moraju se izvesti i jedan i drugi posao.

2
z2
1
z1
1 2 t
Slika 2.2 - Paralelno izvođenje poslova

Dva posla se izvode isključujući jedan drugoga (alternativno) ako se ovisno o


vrijednosti ulaznih podataka treba izvesti samo jednoga. I tada je najkraće vrijeme
izvođenja sigurno jednako duljem vremenu izvođenja, ali je dovoljan samo jedan
procesor, jer se izvodi samo jedan posao (sl. 3.3). Logika izvođenja je "ILI", tj. mora se
izvesti samo jedan posao.
PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA PROGRAMA 129

2
1
z2
1 2 t
Slika 2.3 - "ILI" logika izvođenja

Ovakva početna interpretacija može se poopćiti na složeni skup međusobno zavisnih i


nezavisnih poslova. To će poslužiti za procjenu broja procesora koji zadani skup
poslova mogu izvesti u najkraćem mogućem vremenu. Takva procjena važna je iz dva
razloga kako slijedi:

• da se utvrdi da li kakvi su uvjeti izvođenja poslova u stvarnom vremenu, što se


posebno važno za upravljanje pozivima i uslugama u mreži,
• da se odredi optimalni broj procesora u procesorskom sustavu kako bi se
postiglo ekonomično rješenje.

Tok programa opisuje se grafom koji se sastoji od čvorova i grana. Čvorovi


predstavljaju poslove koji se trebaju obaviti, a grane označavaju redoslijed. Svakom
čvoru pridruženo je vrijeme izvođenja koje označava broj vremenskih intervala
potrebnih za obavljanje dotičnog posla. Grane grafa moraju biti usmjerene od
poslova koji se obavljaju ranije prema poslovima koji se obavljaju kasnije. U grafu ne
smiju postojati petlje.
Neka je zadan je skup identičnih procesora Pi, i = 1,2,... koji moraju izvršiti skup
poslova Z = {z1,z2,...,zj,...,zN}. Također je zadan redoslijed izvođenja poslova te
funkcija µ:Z→ µ (0,¥) koja označava nenegativnu vrijednost vremena pridruženu
svakom poslu. Radi pojednostavljenja, umjesto µ (Zi) za označavanje trajanja posla
pišemo ti.
Djelomično uređen skup (Z, <), gdje < označava redoslijed, opisan je ograničenim
acikličkim grafom G = (Z, E), gdje Z označava ograničen skup čvorova odnosno
poslova, a E skup grana. Grane iz skupa E označavaju redoslijed izvođenja.
Pretpostavljamo da graf ima samo jedan ulazni čvor, odnosno čvor bez prethodnika i
samo jedan izlazni čvor, odnosno čvor bez sljedbenika. Ovaj zahtjev ne predstavlja
nikakvo ograničenje, budući da uvijek možemo kao ulazni formirati jedan «prazni»
čvor (s vremenom izvođenja jednak nuli). Čim procesor započne izvođenje nekog
posla, ne može biti prekidan sve dok ne završi posao. Riječ je o neprimitivnom
raspoređivanju poslova. Strategija raspoređivanja poslova se sastoji od dodjeljivanja
onih poslova (čvorova) čiji su prethodnici izvršeni prvom raspoloživom procesoru.
Vrijeme kad je obavljen zadnji posao označavamo s ω.
Dužina kritičnog puta grafa je minimalno vrijeme potrebno za izvršenje skupa poslova
i označava se sa tcp. Poznavajući sve elemente skupa (Z, <) možemo odrediti
minimalan broj procesora potrebnih za obavljanje skupa poslova tako da je ω = tcp
ili možemo odrediti minimalno vrijeme poznavajući broj raspoloživih procesora m.
Najranije izvođenje posla zi je najranije vrijeme kad posao može biti završen
zadovoljavajući redoslijed operacija zadan grafom. Definirano je izrazom:
PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA PROGRAMA 130

i = max
k

gdje je pk k-ti put od ulaznog čvora do čvora i.


Najkasnije izvođenje posla pokazuje kako dugo se može odgađati izvođenje tog
posla, a da se pritom ne poveća dužina kritičnog puta. Definirano je izrazom:
ti=

gdje je tcp dužina kritičnog puta grafa G, a pk k-ti put od izlaznog čvora do čvora i
(preusmjeravanjem svih strjelica u grafu). Interval [ti ,ti ] je interval izvođenja za posao
zi i pokazuje interval unutar kojeg posao mora biti izvršen kako ne bi došlo do
porasta dužine kritičnog puta. Također je još potrebno definirati veličine aktivnost
čvora i funkciju gustoće opterećenja i to:

aktivnost čvora: f(ti, t) =

funkcija gustoće opterećenja: F(t, t) =

Pri tome f(ti, t) pokazuje aktivnost čvora i (odnosno posla zi) tijekom vremena. F(t, t)
pokazuje ukupnu aktivnost grafa kao funkciju vremena.

2.2. Donja granica minimalnog broja procesora i


minimalnog vremena
Razmatramo određeni interval [Q1,Q2] unutar intervala [0, tcp]. Svaki posao smije biti
pomaknut unutar svog intervala izvršenja [ti ,ti] , a da ne dođe do povećanja dužine
kritičnog puta.

Teorem1:
Neka je R(Q1,Q2,t) funkcija gustoće poslova unutar intervala [Q1,Q2] = [0, tcp].
Donja granica minimalnog broja procesora za izvršenje grafa unutar tcp je dana
izrazom:

mL =

gdje je maksimum uzet preko svih cjelobrojnih vremenskih segmenata [Q1,Q2], a é ù


znači najveću cjelobrojnu vrijednost veću od vrijednosti dobivenog izraza (najveće
cijelo).

Dokaz:
Razmotrimo neki interval [Q1, Q2] Ì [0, tcp]. Kako je R(Q1,Q2,t) funkcija gustoće
poslova koji moraju biti procesirani unutar tog intervala, srednja vrijednost pokazuje
minimalan broj procesora potrebnih za taj interval. Maksimalna vrijednost postignuta
za sve moguće intervale pokazuje da cijeli graf ne može biti izvršen sa brojem
PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA PROGRAMA 131

procesora manjim od tog maksimuma. Način računanja mL prikazan je na primjeru, s


tim da se ime svakog posla pojavi onoliko puta koliko mu je vremenskih jedinica
potrebno za izvođenje. Npr. najranije i najkasnije izvođenje poslova u nekom
intervalu prikazano je na slici 3.4.

Slika 2.4 - Najranije i najkasnije izvođenje

f( t ,[Q1,Q2]) Þ F = {b, a, a} f( ,[Q1,Q2]) Þ F = {b, a, a, c}


F =3 =4
F Ç F = {b, a, a}
ç F Ç F ç= 3

Lema1:
Sljedeći izraz za mL je jednak izrazu prema teoremu1:
é é F ! F ùù
mL = ê max ê úú .
êê[Q1,Q 2 ]êë Q2 - Q1 úû úú
Dokaz:
f(t,[Q1,Q2]) se sastoji od poslova ili dijelova poslova u intervalu [Q1,Q2]. Presjek f( t
,[Q1,Q2]) i f( ,[Q1,Q2]) pokazuje dijelove poslova koji ne mogu biti izvršeni prije
intervala [Q1,Q2], niti nakon tog intervala, budući da poslovi ne mogu biti pomicani u
vremenu ispred njihovih vremena najranijeg izvođenja ni poslije vremena najkasnijih
izvođenja. Drugim riječima, taj presjek sadrži dijelove poslova koji moraju biti
obrađeni unutar intervala [Q1,Q2] i kao takav je ekvivalentan R(Q1,Q2,t) iz
teorema1. Ostatak izraza je jednak sa onim iz teorema1, što potvrđuje jednakost tih
izraza. Isti koncept iskorišten za ostvarenje minimalnog broja procesora može se
primijeniti i za donju granicu minimalnog vremena potrebnog za izvršenje skupa
poslova ako je poznat fiksan broj procesora m. Opći oblik izraza je ti = tcp + éq ù
gdje je q porast vremena iznad kritičnog puta kad nema dovoljno procesora za
obavljanje svih paralelizama grafa i ima nenegativnu vrijednost.

Teorem2:
Porast vremena izvođenja iznad dužine kritičnog puta kad je na raspolaganju m
procesora dano je izrazom:
é 1 Q2 ù
q = max ê- (Q2 - Q1) + ò R(Q1,Q2, t ) ú
[Q1,Q 2 ]ë m Q1 û
PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA PROGRAMA 132

Dokaz:
Za svaki mogući interval [Q1,Q2] razmatramo razliku između njegovog opterećenja
tako da je w = tcp i efektivne aktivnosti koja mora biti obavljena sa raspoloživim
procesorima m. Područje prekoračenja podijeljeno sa m daje porast vremena iznad
tcp.

2.3. Postupak procjene minimalnog broja procesora i


trajanja programa
Postupak se sastoji od sljedećih koraka:

1. Zadati graf poslova za slijedno izvođenje (ILI graf).


2. Pretvoriti ILI graf u I graf s najvećim mogućim paralelizmom poslova (ILI ® ILI/I
® I).
3. Odrediti kritični put.
4. Rasporediti poslove uz najranije i najkasnije izvođenje, te tako da broj procesora
bude minimalan.

Primjer: Za zadani graf poslova odrediti minimalni broj procesora na koje treba
rasporediti poslove uz najkraće vrijeme izvođenja.

1. Na slici 3.5a je prikazan graf poslova za slijedno izvođenje (ILI logika).

2. Sljedeći korak je odrediti graf poslova uz maksimalni paralelizam izvođenja


prikazan je na slici 3.5b (ILI/I logika).
Kako postoje dvije grane (ILI grana) uklanja se ona koja ima kraće vrijeme
izvođenja pa prema tome ostaje grana u kojoj se nalaze poslovi z4 i z6, a
uklanjamo granu sa poslovima z3 i z5. Kao završni posao uveden je “fiktivni”
posao s vremenom izvođenja nula. Slika 3.6 prikazuje graf toka programa za
paralelno izvođenje (I logika).

3. Kritični put obuhvaća poslove z1, z2 ,z4 ,z6, z7, z10, z11 i z12. Prema tome
minimalno vrijeme izvođenja je: Tmin = t1 + t2 + t4 + t6 + t7 + t10 + t11 + t12
= = 1 + 1 + 2 + 1 + 2 + 1 + 1+ 1 = 10 vremenskih jedinica. Podebljanom
linijom označen je kritični put grafa (slika 3.6).
PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA PROGRAMA 133

a a
1 z1 1 z1
b b
b b
1 z2 ILI 1 z2 ILI
c1 c2
c1,c2
c1 c2 c1 c2
1 z3 z4 2 1 z3 2 z4
d1 d2 d1 d2
d1 d2 d1 d2
1 z5 z6 1 1 z5 1 z6
e e e e
e e e
2 z7 2 z7 I
f1,f2 f1,f2

f1 f1 f2
f1,f2
1 z8 1 z8 1 z10 1 z9
g g i h
f2
1 z9 i
h 1 z11
j
f1,f2
1 z10 j
i 1 z12

i
1 z11
j
j
1 z12

Slika 2.5a - Graf poslova za Slika 3.5b - Graf poslova uz


sekvencijalno izvođenje maksimalni paralelizam

a
1 z1
b
b
1 z2
c1,c2
c2
2 z4
d2
d2
1 z6
e
e
I
2 z7
f1,f2

f1 f1,f2 f2
1 z8 1 z10 1 z9
g i h

i
1 z11
j
j
1 z12

0 z13

Slika 2.6 - Graf poslova za paralelno izvođenje


PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA PROGRAMA 134

Na slikama 3.7a i 3.7b je prikazan raspored poslova uz najranije i najkasnije


izvođenje. Na temelju tih rasporeda moguće je odrediti odnosno procijeniti minimalan
broj procesora. Uzimamo sve intervale [Q1,Q2] trajanja 1, 2, 3, ... tcp i računamo:

Slika 2.7a i 3.7b - Raspored poslova uz najranije i najkasnije izvođenje

npr. za interval Q1=7, Q2=9:


f( t ,[Q1,Q2]) Þ F = {z8,z9,z10,z11} f( ,[Q1,Q2]) Þ F = {z10,z11}
F =4 =2
F Ç F = { z10,z11}
ç F Ç F ç= 2, mL = 1

Potpuni podaci optimizacije nalaze se u tablici na slici 2.8.

[Q1, Q2] Ç m [Q1, Q2] Ç m [Q1, Q2] Ç m [Q1, Q2] Ç m


L L L L
0,1 1 1 4,6 2 1 1,5 4 1 2,8 6 1
1,2 1 1 5,7 2 1 2,6 4 1 3,9 6 1
2,3 1 1 6,8 2 1 3,7 4 1 4,10 8 2
3,4 1 1 7,9 2 1 4,8 4 1 0,7 7 1
4,5 1 1 8,10 2 1 5,9 4 1 1,8 7 1
5,6 1 1 0,3 3 1 6,10 6 2 2,9 7 1
6,7 1 1 1,4 3 1 0,5 5 1 3,10 9 2
7,8 1 1 2,5 3 1 1,6 5 1 0,8 8 1
8,9 1 1 3,6 3 1 2,7 5 1 1,9 8 1
9,10 1 1 4,7 3 1 3,8 5 1 2,10 10 2
0,2 2 1 5,8 3 1 4,9 5 1 0,9 9 1
1,3 2 1 6,9 3 1 5,10 7 2 1,10 11 2
2,4 2 1 7,10 5 2 0,6 6 1 0,10 12 2
3,5 2 1 0,4 4 1 1,7 6 1
Slika 2.8 - Tablica optimizacije
PROCJENA BROJA PROCESORA I TRAJANJA IZVOĐENJA PROGRAMA 135

Na temelju tablice optimizacije zaključuje se da su za zadani graf poslova potrebna


najmanje dva procesora. 2.9 prikazuje jedan od mogućih optimalnih rasporeda.

Slika 2.9 - Optimalan raspored poslova

Nakon što je obavljena procjena minimalnog broja procesora, postavlja se pitanje


kako pronaći optimalan raspored poslova, odnosno raspored s minimalnim vremenom
izvođenja za specificirani broj procesora. U sljedećem poglavlju je opisan genetički
algoritam koji pronalazi optimalan raspored poslova za specificirani broj procesora,
uz mogućnost komunikacije među poslovima.
U ovom postupku idealizirano je pokretanje i završavanje programa tako da je
pretpostavljeno da traju 0 vremenskih jedinica. Isto tako, idealizirana je komunikacija
između dva programa tako da je pretpostavljeno da je izmjena podataka između
dva programa na istom ili različitim procesorima trajanja 0 vremenskih intervala. To
je prihvatljivo za početnu procjenu, a detaljnije bi analize trebale uključiti uvjete
izvođenja i komunikacije programa.

2.4. Uravnoteženje opterećenja procesora

Raspored poslova (slika 2.9) je optimalan s motrišta najkraćeg trajanja i najmanjeg


broja procesora potrebnog da se postigne najkraće trajanje. Međutim, raspored nije
dobar s motrišta opterećenja procesora, jer je jedan procesor opterećena svih 10
intervala (100%), a drugi samo 2 intervala (20%) . U stvarnim sustavima procesor ne
smije biti potpuno opterećen, jer u tom slučaju ne može obaviti nikakav sustavski
posao, kao što je npr. ispitivanje sklopovskih jedinica ili dijagnostika kvara. Isto tako,
procesor ne bi mogao prihvatiti nikakvo, pa niti trenutno preopterećenje.

Stoga se procesori nastoje opteretiti podjednako, odnosno uravnotežiti opterećenje


prebacivanjem poslova s više opterećenog na manje opterećeni procesor. Jedna od
mogućnosti predočena je slikom 2.10.

mL

3
2
z2 z4 z6 z8 z9
1
z1 z7 z10 z11 z12
t
1 2 3 4 5 6 7 8 9 10

Slika 2.10 – Uravnoteženje opterećenja


RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 136

3. RASPOREĐIVANJE POSLOVA U PARALELNIM I


RASPODIJELJENIM SUSTAVIMA
Raspoređivanje poslova (scheduling) javlja se uvijek kad treba odlučiti u kojem
redoslijedu izvoditi poslove, odnosno dodjeljivati poslove poslužitelju na obradu.
Zadaća raspoređivanja je odrediti koji se posao dodjeljuje kojem procesoru i u kojem
redoslijedu da bi se postigle neke performanse, npr. najkraće vrijeme izvođenja
programa. Problem je težak i ne može se u općem slučaju egzaktno riješiti. Može se
formulirati ovako: skup resursa treba dodijeliti skupu potrošača sukladno nekim
pravilima, uvažavajući ograničenja koja unose potrošači i resursi.
Raspoređivanje nas zanima iz dva razloga: analiza i izvedba distribuiranih i
paralelnih sustava.
Raspoređivanje može biti determinističko ili nedeterminističko. Kod determinističkog
raspoređivanja sve su informacije o poslovima i njihovim odnosima poznate prije
izvođenja (graf poslova, troškovi izvođenja i komuniciranja), dok to kod
nedeterminističkog nije slučaj.
Nedeterministički postupci mogu biti

• statički: graf poslova koji predočuje program poznat je prije izvođenja,


pretpostavlja se neko izvođenje poslova,
• dinamički: poslovi se raspoređuju tijekom izvođenja, najčešće na načelu
uravnoteženja opterećenja,
• hibridni: dio poslova se raspoređuje statički, a dio dinamički.

Deterministički postupci su statički, a dijele se na sljedeće pristupe:

• skup nezavisnih poslova: poslovi se dodjeljuju procesorima (allocation) tako da


ukupni troškovi obrade i komunikacije budu minimalni, što je tipično za
distribuirane sustave,
• skup zavisnih poslova: definirana je djelomična uređenost poslova tako da
problem postaje nerješiv u općem slučaju.

Za zavisne poslove, ako se ne razmatra komunikacija, optimalna rješenja se mogu


iznaći za poslove jednakog trajanja ako je graf poslova oblika stabla, ili oblika
intervalnog poretka (zavisni poslovi se ne preklapaju), ili ako je riječ o samo dva
procesora. Uz razmatranje komunikacije optimalno rješenje je moguće odrediti ako su
trajanja izvođenja poslova i komunikacije između njih jednaka. U svim ostalim
primjerima primjenjuju se iskustveni postupci.

Moguće su i druge sistematizacije raspoređivanja, npr.:

• jednoaplikacijski/višeaplikacijski sustav, odnosno raspoređivanje svake aplikacije


zasebno ili miješanjem poslova ili različitih aplikacija
• neprekinuto izvođenje/izvođenje s prekidanjem poslova, tako da se svaki
započeti posao izvodi do kraja ili prekida u izvedbi npr. zbog nailaska posla
višeg prioriteta, predugog izvođenja i sl.
• neadaptivno/adaptivno, odnosno bez ili s promjenama pravila ili načina
odlučivanja o rasporedu ovisno o postignutom rezultatu.
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 137

3.1. Model raspoređivanja


Model raspoređivanja obuhvaća:

• procesorski sustav,
• skup poslova,
• trošak izvođenja i komunikacije (najčešće se izražava trajanjem),
• raspored i
• mjeru uspješnosti.

U općem slučaju razmatra se mreža različitih procesora, povezanih svaki sa svakim.


Svaki procesor izvodi jedan posao u jednom trenutku, a svaki posao se može izvesti
na svakom procesoru. Formalno, riječ je o ovakvom sustavu:

(P, [Pij], [Si], [Ii], [Bi], [Rij])

gdje je:

P = {P1, …, Pm} - skup od m procesora


[Pij] - m x m matrica topologije (Pij = 1 ako su Pi i Pj povezani,
inače 0)
[Si] - brzina procesora Pi
[Ii] - trošak izmjene poruke za procesor Pi
[Bi] - trošak pokretanja posla na procesoru Pi
[Rij] - brzina prijenosa na vezi između procesora Pi i Pj

Grafički prikaz procesorskog sustava je neusmjereni graf čiji su čvorovi procesori, a


grane veze između njih.

Pij = 1
Pi Pj
Rij
Si, Ii, Bi Sj, Ij, Bj

Slika 3.1 – Grafički prikaz procesorskog sustava

Skup poslova modelira se kao djelomično uređeni skup (Z, <) gdje je Z skup poslova,
a relacija u < v govori da posao v ovisi o rezultatu posla u, tako da se u izvodi prije
v.

Paralelni program predočen je ovako:

(Z, <, [Dij], [Ai])

gdje je:

Z = {z1, …, zn} - skup od n poslova


< - relacija uređenosti
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 138

[Dij] - n x n matrica, Dij govori koliko podataka se prenosi iz posla


zi u zj ,
[Ai] - broj instrukcija pri izvođenju posla zi

Grafički prikaz skupa zavisnih poslova je aciklički usmjereni graf čiji su čvorovi
poslovi, a grane predočuju njihovu zavisnost.

<
zi zj
Dij
Ai Aj

Slika 3.2 – Grafički prikaz skupa poslova

Trajanje izvođenja posla zi na procesoru Pj iznosi:

Tij = Ai/Sj + Bj

Komunikacijsko kašnjenje između poslova zi1 i zi2 kad se izvode na procesorima Pj1 i
Pj2 iznosi:

C(i1, i2, j1, j2) = Di1i2/Rj1j2 + Ij1

Ako se šalje više poruka po istoj vezi mora se uračunati dodatno kašnjenje CDj1j2 koje
odgovara trajanju prijenosa poruke:

C(i1, i2, j1, j2) = Di1i2/Rj1j2 + Ij1 + CDj1j2

Ako izvorni i odredišni procesor nisu susjedni već poruka mora proći procesore k1, k2,
…, kz komunikacijsko kašnjenje će iznositi:

C(i1, i2, j1, j2) = Di1k1/Rj1k1 + Ij1 + CDj1k1 + Σ(Dkiki+1/Rkiki+1 + Iki + CDkiki+1) +
Dkzj2/Rkzj2 + Ikz + CDkzj2

Kad bi svi procesori bili jednaki tako da iniciraju prijenos poruke za neko vrijeme I te
brzine prijenosa jednake R na svim vezama, pojednostavljena formula bi glasila:

C(i1, i2, j1, j2) = (Di1i2/R + I)(z+1)+ CDj1j2

Raspored poslova na procesorski sustav P je funkcija f koja preslikava svaki posao na


neki procesor, s nekim početnim trenutkom izvođenja:

f : Z → {1, 2, …,m} x [0, ∞)

Ako je f(v) = (i, t) za neki z ε Z, kažemo da se posao z izvodi na procesoru Pi s


početkom u trenutku t.
Raspored je prihvatljiv ako su uređenost skupa poslova i komunikacijska ograničenja
očuvani.
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 139

Na primjeru sustava s dva procesora i pet poslova prikazat će se raspoređivanje sa

P1 P2
Rij =1
1
S1 = 1, I1 =0, B1 =0 S2 =1, I2 =0, B2 =0
zadanim trajanjem obrade i komunikacijom među poslovima (slika 4.3 – 4.5).

Slika 3.3 – Procesorski sustav

A2 = 10
z1 z2

1
A1 = 10
1
A3 = 20

z4
z3 A4 = 20
D15 = 1
1
1

z5 A5 = 5

Slika 3.4 – Skup poslova

D35

P2 z2 z3

P1 z1 z4 z5

10 20 30 36 t
D24

Slika 3.5 – Raspored

Uobičajeni kriteriji za procjenu uspješnosti raspoređivanja su minimalno vrijeme


izvođenja programa i uravnoteženost opterećenja.

3.2. Raspoređivanje poslova genetičkim algoritmom


Problem raspoređivanja poslova po procesorima pripada grupi optimizacijskih
problema za koje se mogu primijeniti genetički algoritmi.
Genetički algoritam pripada skupini stohastičkih algoritama za različite probleme
optimizacije. Temelji se na prirodnim principima selekcije i opstanka najboljeg člana
populacije koji prelazi u sljedeću generaciju prenoseći u nju svoja dobra svojstva koja
će se kombinirati sa svojstvima ostalih članova populacije i tako podizati kvalitetu
cijele generacije.
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 140

3.2.1. Opis problema


Općenito, ovaj problem je teško rješiv čak i pri pojednostavljenim pretpostavkama.
Pristup rješavanju je zasnovan na determinističkom modelu: vrijeme izvođenja
pojedinog posla, međusobni odnosi među poslovima i vrijeme komunikacije među
poslovima su poznati. Relacije prethodnosti između poslova prikazane su acikličkim
usmjerenim grafom. Vrijeme izvođenja pojedinog posla je navedeno kraj samog
posla, a vrijeme komunikacije dva posla na grani koja povezuje ta dva posla (sl. 4.6).
Daljnje pretpostavke su jednakost procesora i dovršavanje izvođenja tekućeg posla
od strane procesora, prije nego što počne izvođenje sljedećeg posla.
Što se tiče komunikacije poslova, treba pretpostaviti da ako sa dva međusobno
zavisna posla izvršavaju na različitim procesorima, izvršavanje sljedećeg posla može
početi tek nakon što se završi sa izvođenjem tekućeg i obavi vrijeme komunikacije ta
dva posla.
Trenutak završetka posljednjeg posla naziva se vrijeme izvršenja rasporeda (finishing
time). Donja granica vremena izvođenja za bilo koji raspored, određena je kritičnim
putom koji je definiran kao put kroz graf toka programa za čije izvršenje je potrebno
minimalno vrijeme uz uvjet obavljanja cijelog procesa.
Genetički algoritam (GA) se sastoji od sljedećih koraka:

1. Slučajno generiranje početne populacije (inicijalizacija);


2. Izračunavanje vrijednosti funkcije prikladnosti za svaki član populacije
(jednostavna selekcija);
3. Izvođenje genetičkih operatora nad članovima populacije;
4. Ponavljanje drugog i trećeg koraka dok se ne ispune uvjeti završetka
algoritma.

Stvaranje genetičkog algoritma sastoji se od četiri cjeline:

• odabir načina predstavljanja poslova;


• oblikovanje genetičkih operatora;
• određivanje funkcije prikladnosti;
• određivanje vjerojatnosti izvođenja genetičkih operatora.

Za svaki posao zi unutar rasporeda, definiraju se dva skupa poslova:

• skup prethodnika koji obuhvaća sve poslove koji se moraju u potpunosti izvršiti
prije nego što počne izvršavanje dotičnog posla;
• skup sljedbenika koji obuhvaća sve poslove koji mogu početi s izvršavanjem
tek nakon što se dotični posao u potpunosti izvrši.

Dva prethodno opisana skupa poslova, omogućuju definiranje veličine pod nazivom
visina h(zi) koja je dodijeljena svakom poslu. Visina poprima iznos nula za sve poslove
čiji je skup prethodnika prazan, dok za ostale poprima iznos za jedan veći od
maksimalne visine posla unutar skupa prethodnika.
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 141

3.2.2. Graf poslova


Graf poslova je aciklički, usmjereni graf, G=(Z,E). Slika 4.6 prikazuje primjer grafa
poslova.
z1 t1,v1
c12 c13
e12 e13
z2 t2,v2 z3 t3,v3

c24 e24 c35 e35


e36

z4 t4,v4 z5 t5,v5 z6
e47
e67 t6,v6
c47 e57 c57 c67

z7 t7,v7

Slika 3.6 - Graf poslova

Z predstavlja skup poslova Z = {z1,z2,...,zj,...,zN}, dok E predstavlja skup usmjerenih


grana eij između poslova zi i zj. Grane definiraju odnose prethodnosti i sadrže
parametar cij koji predstavlja trajanje komunikacije između poslova zi i zj.
Uz svaki posao zi nalaze se parametri:

• vrijeme izvođenja ti;


• visina vi,

a uz svaku granu nalazi se parametar: vrijeme komunikacije cij između poslova zi i zj.

3.2.3. Međuprocesorska komunikacija


Posao ne može početi s izvršavanjem prije nego što njegov prethodnik ne završi sa
svojim izvršavanjem. Ako se dva posla zi i zj izvršavaju na različitim procesorima Pk i
Pl, izvršavanje posla zj može početi tek nakon što završi izvođenje posla zi i nakon što
se obavi komunikacija cij. Dakle, ako posao zi započne izvršavanje u nekom trenutku
tx, najranije vrijeme izvršenja za te poslove jednako je: tx+ti+cj+tj (slika 4.7). U
slučaju da se oba posla nalaze na istom procesoru, komunikacija nije potrebna i
vrijeme izvođenja se reducira na tx+ti+tj.

Pl zj

Pk zi

C eij
t
tx tx+ti tx+ti+cij tx+ti+cij+tj

Slika 3.7 - Komunikacija među poslovima

Ako se neki od prethodnika, zhÎPZi, (ili svi) izvršava na različitom procesoru od


promatranog posla zi, treba još uzeti u obzir vrijeme komunikacije chi, pa tek onda
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 142

krenuti sa izvršavanjem promatranog posla zi. Analogno, ako se neki od sljedbenika,


zjÎSZj, (ili svi) izvršavaju na različitim procesorima od promatranog posla zi,
izvršavanje sljedbenika treba pričekati dok se ne završi komunikacija cj. Ako se
prethodnici i sljedbenici nalaze na istom procesoru kao i promatrani posao,
komunikacija nije potrebna.

Graf poslova sa slike 4.6 sastoji se od 7 poslova (čvorova) i 8 grana koje povezuju
čvorove i određuju odnose prethodnosti prilikom izvođenja. Radi jednostavnije analize
grafa poslova, svaku granu možemo predočiti kao čvor (posao) koji ima svoje vrijeme
izvođenja (slika 4.8). Dakle, vrijeme komunikacije između poslova ti i tj u stvari
predstavlja vrijeme izvođenja novog posla cij. Na taj se način razmatranje
komunikacije (grana) svodi na razmatranje poslova, što pojednostavljuje
prilagođavanje problema genetičkom algoritmu. Novonastali poslovi nazivaju se
komunikacijski poslovi koji se uključuju u raspored ovisno o tome da li se njihovi
prethodnici i sljedbenici izvršavaju na različitim procesorima ili ne. Originalni poslovi
moraju se uvijek uzeti u obzir sa svojim vremenom izvođenja ti, dok se komunikacijski
poslovi uključuju s vremenom izvođenja cij ili 0, ovisno o lokaciji njihovog prethodnika i
sljedbenika.

z1 t1,v1
c12 e
12 e13 c13

z2 t2,v2 z3 t3,v3
T1

c24 e24 c35 e35 e36

z4 t4,v4 z5 t5,v5 z6
c67
e47 e57 c57 e67

z7 t7,v7

Slika 3.8 - Graf s komunikacijskim poslovima kao granama

3.3. Početna populacija


Rasporedi se prikazuju nizovima koji se sastoje od lista. Svaka lista predstavlja
poslove dodijeljene jednom procesoru (slika 4.9). Ovakav prikaz eliminira potrebu
razmatranja odnosa prethodnosti i trajanja komunikacije između poslova dodijeljenih
različitim procesorima. Te odnose će trebati uzeti u obzir prilikom računanja funkcije
prikladnosti.

Početna populacija predstavlja specificirani broj slučajno generiranih rasporeda


(nizova) pri čemu mora vrijediti sljedeće:
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 143

• očuvanje odnosa prethodnosti među poslovima;


• svaki posao se pojavljuje jednom i samo jednom u svakom nizu (kompletnost i
jedinstvenost).
Ukoliko niz ne zadovoljava ove uvjete, on je ilegalan.
Neka je zadan graf poslova prema slici 4.6. Ako poslove želimo rasporediti na dva
procesora, početna populacija će se sastojati od nekoliko nizova (ovisno o veličini
populacije), a svaki niz će imati dvije liste. Jedan od mogućih rasporeda prikazan je
na slici 4.9.

visina 0 1 2 3

P1 z1 z3 z5 z7 1
lista

raspored A
1 2 2
P2 lista 2
z2 z4 z6

Slika 3.9 - Prikaz rasporeda (niza) listama

U početnoj populaciji prilikom generiranja rasporeda nije potrebno voditi računa o


odnosima prethodnosti među listama, već samo unutar lista. Prema tome, pošto svaka
lista prikazuje redoslijed izvođenja na jednom procesoru, nije potrebno voditi računa
niti o vremenima komunikacije među poslovima. Bitno je samo da se poslovi unutar
liste rasporede tako da visine idu od manjih prema većim (ili jednakim) vrijednostima,
nikako s veće na manju. U našem slučaju su visine 0-1-2-3 za P1 i 1-2-2 za P2, što
znači da su sačuvani odnosi prethodnosti unutar liste. Algoritam generiranja početne
populacije sastoji se od sljedećih koraka:

1. Izračunati visinu h za svaki posao iz grafa G.


2. Razdvojiti poslove u različite skupove G(h), gdje je G(h) skup poslova s
visinom h.
3. Za prvih M-1 procesora (M je ukupan broj procesora) učiniti korak 4.
4. Za svaki skup G(h) neka je NG(h) broj poslova u skupu G(h). Slučajno
generirati cijeli broj između 0 i NG(h). Izdvojiti r poslova iz G(h) te ih
dodijeliti aktualnom procesoru. Smanjiti NG(h) za r.
5. Preostale poslove dodijeliti zadnjem procesoru.

3.4. Funkcija prikladnosti


Funkcija prikladnosti je funkcija cilja koju nastojimo optimizirati. Ovdje razmatrana
funkcija prikladnosti bit će temeljena na vremenu izvršenja rasporeda. Vrijeme
izvršenja rasporeda S je definirano izrazom: FT(S)=max ft(Pj), gdje je ft(Pj) vrijeme
izvršenja zadnjeg posla na procesoru Pj, a maksimum je uzet za sve procesore P.

Budući da genetički operatori nastoje maksimizirati funkciju prikladnosti, vrijeme


izvršenja rasporeda treba prevesti u maksimizacijski oblik Cmax - ft(S), pri čemu je
Cmax konstanta (u našem slučaju dobivena kao zbroj trajanja svih poslova i svih
komunikacija među poslovima grafa). Algoritam izračunavanja funkcije prikladnosti
sastoji se od sljedećih koraka:
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 144

1. Ponavljati korake od 2-4. za sve parove procesora Pi-Pj unutar niza.


2. Poredati poslove po procesorima neposredno jedan iza drugoga.
3. Ponavljati korak 4. sve dok se nije pojavila barem jedna promjena u
rasporedu.
4. Uzimamo u razmatranje po jedan posao iz svake liste (od svakog procesora).
Ovisno o lokaciji prethodnika, dotični posao smještamo u raspored, uzimajući
u obzir vrijeme komunikacije, ako je to potrebno. Nakon toga, uzimamo novi
posao i postupak ponavljamo dok ne razmotrimo sve poslove oba niza.
5. Pregledati sve liste unutar rasporeda i pronaći vrijeme izvršenja rasporeda.
Izračunati funkciju prikladnosti prema formuli:

FV(S)=Cmax-ft(Pj).

3.5. Genetički operatori


Genetički operatori služe za kreiranje novih nizova na temelju aktualne populacije.
Novi nizovi se dobivaju kombiniranjem ili preuređenjem starih nizova. Na taj način se
mogu dobiti bolji nizovi, ali i gori (s manjim iznosom funkcije prikladnosti). Genetički
operator reprodukcije će boljim nizovima pružiti veću šansu za prelazak u novu
generaciju. Najvažnije je za genetske operatore da ne generiraju ilegalne nizove.
Ako genetički operatori ipak mogu generirati ilegalne nizove, bit će potrebno
provoditi provjeru legalnosti za svaki niz.

3.5.1. Križanje
Operator križanja razmjenjuje dijelove dvaju nizova i kreira dva nova niza. Neka je
zadan graf poslova kao u prošlom primjeru (slika 4.6), uz pretpostavku da je vrijeme
izvođenja svakog posla ti=1, a vrijeme komunikacije cij=1 (radi jednostavnosti).
Razmatramo dva slučajno generirana niza prikazana na slici 4.10a. Na slici 4.10b
prikazani su rasporedi tih nizova po procesorima. Postupak križanja se provodi na
sljedeći način:

1. Pronaći mjesta (točke križanja) gdje možemo liste prerezati na dva dijela
(određivanje točaka križanja objašnjeno je u nastavku).
2. Razmijeniti stražnje dijelove lista 1 niza A i niza B.
3. Razmijeniti stražnje dijelove lista 2 niza A i niza B.

Na taj način dobivamo nove nizove prikazane slikom 4.11a, a njihov raspored po
procesorima prikazan je na slici 4.11b.
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 145

visina 0 1 2

P1 z1 z3 z4 lista 1

niz A
1 2 2 3

P2 z2 z6 z5 z7 lista 2

visina 0 1 2 3
z1 z2 z5 z7
P1 lista 1
niz B
1 2 2

P2 z3 z4 z6 lista 2

točke križanja
Slika 3.10a - Nizovi prije križanja

Slika 4.10b - Rasporedi prije križanja


RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 146

visina 0 1 2 3

P1 z1 z3 z5 z7 lista 1

niz A
1 2 2

P2 z2 z4 z6 lista 2

visina 0 1 2

P1 z1 z2 z4 lista 1

niz B
1 2 2 3

P2 z3 z6 z5 z7 lista 2

Slika 3.11a - Nizovi nakon križanja

Slika 4.11b - Rasporedi nakon križanja

Prema slici 4.11b vidi se da niz B, koji je dobiven križanjem ima manji FT od
prethodna dva niza. Dakle dobili smo bolji niz. Operacija križanja se može proširiti
na neograničen broj procesora.
Ostalo je još definirati način na koji se pronalaze točke križanja te pokazati da su svi
nizovi dobiveni križanjem legalni.

Teorem: Ako su točke križanja izabrane tako da zadovoljavaju sljedeće uvjete:


• visine poslova ispred i iza točke križanja su različite i
• visine svih poslova neposredno ispred točke križanja su iste (pri tome ako je
točka križanja na samom početku liste, kao visina posla ispred te točke može
se računati bilo koja visina), onda će generirani raspored sigurno biti legalan.
Dokaz:
Treba pokazati da su sačuvani odnosi prethodnosti, kompletnosti i jedinstvenosti.
Zadovoljeni su uvjeti: h(zi) á h(zj), za niz A; h(zk) á h(zl), za niz B te h(zi)=h(zk). Budući
da su između rasporeda razmijenjeni svi poslovi s visinom većom od h(zi), sačuvani su
kompletnost i jedinstvenost. Kako je h(zi) á h(zl) (posao zi se izvršava prije posla zl) i
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 147

h(zk) á h(zj), znači da su sačuvani i odnosi prethodnosti među poslovima, a to znači da


su novi rasporedi legalni.

Algoritam križanja se sastoji od sljedećih koraka:

1. Slučajno generirati cijeli broj c između 0 i maksimalne visine u grafu G.


2. Za svaki procesor Pi u nizovima A i B učiniti korak 3.
3. Naći zadnji posao zji u procesoru Pi koji ima visinu c, a zki je posao koji slijedi
zji. Tako je c=h(zji) á h(zki), a h(zji) isto za sve i.
4. Za svaki procesor Pi u nizovima A i B učiniti korak 5.
5. Koristeći točke križanje nađene u koraku 3, razmijeniti stražnje dijelove lista
između nizova A i B za svaki procesor Pi.

3.5.2. Reprodukcija
Operator reprodukcije formira novu populaciju nizova odabiranjem starih, ovisno o
veličini funkcije prikladnosti. Nizovi koji imaju veći iznos funkcije prikladnosti imaju
veću vjerojatnost prelaska u novu generaciju. Nizovi koji ne uspiju prijeći u novu
generaciju, propadaju.
Iako najbolji niz u staroj generaciji ima najveću vjerojatnost za prelazak u novu
generaciju, moglo bi se dogoditi, budući da je riječ o stohastičkom procesu, da
najbolji niz propadne. Da bi se to spriječilo, provedena je modifikacija tako da se
najbolji niz sigurno prenosi u novu generaciju. Takav odabir naziva se elitizam.
Algoritam reprodukcije sastoji se od sljedećih koraka:

1. Neka je NPOP broj nizova u populaciji POP.


2. Neka je NSUM suma vrijednosti funkcija prikladnosti svih nizova iz POP. Treba
formirati NSUM odjeljaka i svakom od njih pripadajući niz s obzirom na
funkciju prikladnosti (npr. ako niz A ima iznos funkcije prikladnosti 14, znači
da će biti formirano 14 odjeljaka od kojih će svaki sadržavati niz A).
3. Učiniti 4. korak NPOP-1 puta.
4. Slučajno generirati cijeli broj između 1 i NSUM koji označava jedan od
odjeljaka. Niz koji pripada tom odjeljku spremiti u NEWPOP.
5. Dodati niz sa najvećim iznosom funkcije prikladnosti u NEWPOP.

3.5.3. Mutacija
Mutacija kao genetički operator osigurava raniju konvergenciju algoritma. Sastoji se u
izmjeni mjesta dvaju poslova unutar jednog niza. Treba napomenuti da mutirati mogu
samo poslovi s istom visinom jer će na taj način ostati zadovoljeni odnosi prethodnosti.
Vjerojatnost mutacije je obično mala u odnosu na vjerojatnost križanja. Na slici 4.12a
i 4.12b su prikazani nizovi prije i poslije mutacije.
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 148

Slika 3.12a - Nizovi prije mutacije

Slika 4.12b - Nizovi nakon mutacije

Algoritam mutacije se sastoji od sljedećih koraka:

1. Slučajno odabrati posao zi.


2. Pronaći u nizu jedan od poslova zj s istom visinom hi=hj.
3. Formirati novi niz izmjenom mjesta poslova zi i zj.

3.6. Potpuni algoritam


Potpuni algoritam za pronalaženje optimalnog rasporeda sastoji se od sljedećih
koraka:

1. Inicijalizacija: Izvršiti generiranje rasporeda N puta (N ovisi o veličini


populacije), a dobivene nizove spremiti.
2. Ponavljanje do konvergencije: Ponavljati od 3. do 8. koraka do konvergencije
algoritma. Kriterij konvergencije je broj generacija.
3. Određivanje funkcije prikladnosti: Izračunati vrijednost funkcije prikladnosti za
svaki niz iz populacije.
4. Reprodukcija: Izvršiti algoritam reprodukcije. Niz sa najvećom vrijednošću
funkcije prikladnosti spremiti.
5. Ponavljanje križanja: Ponavljati korak 6 N/2 puta.
6. Križanje: Odabrati dva niza iz populacije. Ako je križanje obavljeno, (ovisno o
vjerojatnosti križanja) spremiti nove nizove. Inače spremiti izabrane nizove.
RASPOREĐIVANJE POSLOVA U PARALELNIM I RASPODIJELJENIM SUSTAVIMA 149

7. Mutacija: Za svaki niz iz populacije izvršiti mutaciju sa određenom


vjerojatnošću. Ako je mutacija izvršena, spremiti novi niz. Inače spremiti stari
niz.
8. Elitizam: Zamijeniti niz s najmanjim iznosom funkcije prikladnosti sa spremljenim
nizom najvećeg iznosa funkcije prikladnosti.
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 150

4. PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA

4.1. Jezici
Zanimaju nas problemi upravljanja telekomunikacijskim procesima u stvarnom vremenu
te ćemo se pri analizi programskih paradigmi rukovoditi zahtjevima te i takve
problemske domene.
Kao što je poznato, programski jezici razlikuju se po skupu postupaka, koncepata i
načela za koje je utvrđeno da pomažu učinkovitom rješavanju neke vrste problema.
Različiti su pristupi klasifikaciji programskih jezika, a najgrubljom podjelom svrstavaju
se u imperativne i deklarativne jezike.
Imperativni jezici zasnivaju se na oponašanju rada računala. Oni zahtijevaju potpuno
specificiranu manipulaciju imenovanim podacima korak po korak. Čovjek piše
program koji predočuje naputak računalu kako da radi. Riječ je o toku upravljanja
(flow control) kojim se odabiru i obrađuju naredba po naredba, u slijedu koji je
odredio programer.
Primjeri imperativnih jezika su C, Algol 68, Pascal, PL/I, Ada, svi s korijenima u jeziku
Algol 60 (tzv. Algolu slični jezici), zatim FORTAN, COBOL, BASIC (ne-Algolski jezici) te
jezici s drukčijim postavkama kao što je npr. PostScript, upravljački jezik za ispisne
naprave.
Deklarativnim jezicima opisuje se što treba napraviti, izbjegavajući naputke računalu
kako da izvodi naredbe. U prvom je planu funkcija koju treba riješiti ili logika
rješenja tako da se i govori o funkcijskim (npr. Lisp) i logičkim jezicima (npr. Prolog).
Kad je riječ o stvarnom vremenu "kako" ili "što" nisu dovoljni. Ispravnost programa ne
ovisi samo o točnosti rezultata nego i o tome "kad" se rezultat pojavio. Vremenska
ograničenja, uz pouzdanost i mogućnost kontrole sklopovskih jedinica, postaju važna
za takve programske jezike.
U telekomunikacijama stvarnovremeni su imperativni jezici posebno razvijani za
mrežnu domenu primjene, kao npr. Plex (Programmimg Language for Exchanges), ili
CHILL (CCITT High Level Language).
Detaljnije klasifikacije programskih jezika nisu jednoznačne. Jedan je razlog različitost
pristupa klasifikaciji, a drugi što mnogi jezici nisu "čisti" već se temelje na više načela.
Podjela koja je dosta prikladna svrstava jezike u strukturne, objektno orijentirane,
funkcijske, logičke, paralelne i jezike baza podataka. Ponekad se strukturni jezici i
jezici baza podataka (SQL) tretiraju samo kao imperativni, a paralelni dopunjuju
distribuiranim i konkurentnim inačicama. Imperativni jezici nazivaju se još i
procedurnima. Za telekomunikacije i programsko inženjerstvo u telekomunikacijama
najvažnije su postavke sljedećih jezika:
• imperativni,
• objektno orijentirani,
• funkcijski,
• logički,
• raspodijeljeni i
• stvarnovremenski.

Prve četiri paradigme temelj su većine jezika opće namjene, dok zadnje dvije dolaze
do izražaja pri specifičnim problemima. Telekomunikacijske primjene obilježavaju
upravo raspodijeljenost i rad u stvarnom vremenu.
Glede procesiranja izvornog programa razlikuju se dva postupka koji započinju
njegovim učitavanjem, analizom i provjerom ispravnosti:
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 151

• interpretiranje kojim se ustanovljena naredba izravno izvodi te


• prevođenje kojim se referenciranjem na skup strojnih instrukcija pridruženih
izvornoj instrukciji generira prevedeni program i povezuje sa skupom strojnih
instrukcija u biblioteci kako bi se dobio izvršni binarni program.
Imperativni jezici se u pravilu prevode, a deklarativni interpretiraju. Zbog toga su
imperativni jezici višestruko brži u izvedbi, što je dodatni razlog njihove
rasprostranjenosti. „Sporost“ deklarativnih jezika ograničavala je njihovu industrijsku
primjenu, a tek su rješenja novih funkcijskih jezika kakav je Erlang dovela do
prihvatljive vremenske i prostorne složenosti. Spomenimo da je jezik Erlang isto tako
započeo s interpretiranjem, da bi se kasnije prešlo na generiranje C makro-naredbi i
njihovo prevođenje.
Programski jezici za telekomunikacijsku domenu trebaju, uz jednostavnost učenja i
primjene, uvažiti karakteristike komunikacijskih procesa:

• modularnost: mnogo jednostavnih procesa, a složeni postupak odabira


procesa
• stvarnovremeni rad, konkurentnost i kontrola vremena
• povijesno osjetljivi algoritmi (history sensitive) - važan pojam stanja,
• jedan proces može utjecati na ponašanje drugog procesa tako da se javljaju
nuspojave (side effect) koje treba ograničiti i ukloniti,
• otkrivanje pogrešaka mora se provesti prije nego one izazovu ozbiljne
posljedice,
• pouzdanosti i učinkovitosti pridonijeti provođenjem detaljnih i raznovrsnih
ispitivanja u fazi prevođenja i generiranja objektnog (izvršnog) koda,
• fleksibilnost pri različitim primjenama i korištenju različitih sklopovskih rješenja.

4.1.1. Struktura programskih jezika


Strukturni aspekti programskih jezika mogu se promatrati na četiri razine. To su:
• leksička razina koja definira leksičke simbole kao što su identifikatori (nazivi
koje rabi programer), ključne riječi (odabrani nazivi jezika), operatori (oznake
operacija), separatori (znakovi za odvajanje), literali (izravno označene
vrijednosti) i komentari (objašnjenja). Za razumljivost i čitljivost programa
važan je i njegov izgled. U telekomunikacijskim primjenama ustrajava se na
uspostavljanju i poštivanju konvencija naziva za identifikatore ne samo zbog
jednoznačnosti već i da bi se povećala čitljivost programa.
• sintaktička razina opisuje pravila slaganja naredbi. Najčešće se rabi
kontekstno neovisna gramatika (context free grammar) u Backus Naurovom
obliku (BNF - Backus Naur Form) ili proširenom Backus Naurovom obliku (EBNF
- Extended Backus Naur Form). Pri određivanju sintaktičke strukture jezika rabe
se ključne riječi i separatori. Sintaksa je važna za pripremu (pisanje) te
interpretiranje i prevođenje programa. Program se može definirati kao slijed
leksičkih simbola koji je napravljen prema pravilima gramatike jezika.
Gramatika se isto tako primjenjuje kad se taj slijed leksičkih simbola pretvara
u jedinke s nekim značenjem otkrivanjem sintaktičkih elemenata (parsing).
• kontekstna razina određuje izvodljivost naredbi (npr. zbrajati se mogu brojevi)
i uvjete za izvođenje operacija (npr. varijable trebaju imati neku prije
definiranu ili izračunatu vrijednost da bi se mogle zbrojiti). Kontekstna razina
posebno je važna za imperativne jezike. Problemi nastaju ako prevoditelj ne
prepoznaje neke kontekstne uvjete.
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 152

• semantička razina opisuje značenje pojmova u jeziku, a programiranjem se


semantika pojedinih pojmova povezuje u semantiku programa.
Sve četiri razine važne su za telekomunikacijske primjene za koje su važne lakoća
pisanja (writeability) i lakoća čitanja (readability), posebice ovo drugo zbog promjena,
proširenja i održavanja programa općenito. Sukobljena su dva zahtjeva: preciznost
jezika potrebna računalu/procesoru koja ga čini složenim i jednostavnost da bi bio
prihvatljiv čovjeku. Ako se još doda neovisnost o računalu/procesoru dobit će se okviri
u kojima se kreće razvoj i primjena programskih jezika u telekomunikacijama.

4.1.2. Složenost programa


Telekomunikacijski programski sustavi su u pravilu veliki i semantički složeni. S gledišta
jezika tri vrste tehnika mogu pomoći, i to:

• dekompozicija (podjela na više manjih problema),


• apstrakcija (ignoriranje nevažnih detalja na siguran način),
• provjera unutrašnje konzistentnosti.

Dekompozicija je dovoljno poznata, pa će se komentirati samo druge dvije tehnike.


Apstrakcijom se naziva postupak kojim se prikrivaju izvedbeni detalji, a korisniku
omogućuje da ih upotrebljava preko sučelja te se čak niti ne dopušta drugi način
rada. Apstrakcija skrivanjem informacije zahtijeva rješenja na razini prevoditelja i
drugih dijelova programskog sustava. Primjeri apstrakcije su objekti i apstraktni tipovi
podataka.
Provjera unutrašnje konzistentnosti zasniva se na kontekstnom ispitivanju. Sve leksičke i
sintaktičke pogreške se lako otkrivaju, dok to ne vrijedi za kontekstne pogreške. Mogu
se otkriti jednostavne pogreške kao nedeklarirani identifikator, krivi broj parametara
u pozivu i sl. Jedino sredstvo koje pridonosi provjeri konteksta je jako tipiziranje
podataka (strong typing) i jaka provjera tipa podataka (strong type checking). Kako
nije moguće provjeriti sve kontekstne uvjete pojavit će se i semantičke pogreške
tijekom izvedbe programa (run-time error).
Kod imperativnih jezika operacije s podacima su dvojake - ugrađene (built-in) i
definirane od programera. Ugrađene zahtijevaju operande s vrijednostima u nekim
granicama, a mnogi jezici traže od programera da odrede domenu operanada za
operacije koje uvode. Prevoditelj tada pri analizi programa provjerava da li
operacije mogu generirati vrijednosti izvan zadane domene te javlja poruke kako bi
spriječio semantičke pogreške (čim više njih). Domena se naziva tip podatka (data
type) ili samo tip (type). Ako se provjeravaju sve nekonzistentnosti riječ je o strogoj ili
jakoj provjeri tipa. Stroga provjera tipa pojavljuje se kod jezika koji se prevode, a
rijetko kod onih koji se interpretiraju (objektno orijentirani, funkcijski) ili čak nikad
(logički).

4.1.3. Pogreške tijekom izvedbe


Pogreške tijekom izvedbe mogu biti ponavljajuće (sinkrone) ako se događaju pri
svakoj izvedbi programa, ili neponavljajuće (asinkrone) ako se događaju katkad.
Pogreške tijekom izvedbe programa nastaju zbog pogreški podataka koje su
sinkrone (npr. dijeljenje s 0), nedostatka resursa kao sinkrone ili asinkrone (npr.
pomanjkanje prostora u memoriji) ili gubitka jedinica koje su asinkrone (npr. prekid
komunikacije); izazvane su iznutra (samim programom) ili izvana (ispad napajanja).
Pogreške će, bez posebnih mjera zaštite izazvati prekid programa. Pri upravljanju u
stvarnom vremenu se to ne može prihvatiti, te jezici moraju dopustiti ponovno
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 153

uspostavljanje kontrole nad programom. Govori se o robusnosti jezika odnosno


programa. Sredstva za to su npr. naredbe izuzetka (exception).

4.1.4. Specifičnosti jezičnih paradigmi

1.36.2.1 Procedurni jezici


Kao što je prije rečeno imperativnim jezicima se specificiraju podaci i postupak
njihove obrade. Tok upravljanja je načelo izvedbe programa naredbu po naredbu
tijekom kojeg podaci mijenjaju vrijednosti.

Podatak

Jedinka podatka je skupina bita u memoriji. Deklaracijom podatka povezuje se naziv


s jedinkom podataka zadane strukture. Naziv ima sintaktičko značenje identifikatora,
a struktura tipa. Jedinka podatka postoji u memoriji, a naziv i struktura samo u
programu.
Varijabla je podatak čija se vrijednost može promijeniti. Inicijalizacijom se podatku
dodjeljuje početna vrijednost (spomenimo da neinicijalizirajuće deklaracije podataka
mogu dovesti do pogrešaka!). Više naziva za isti podatak dobiva se preimenovanjem
(rename) ili dodatnim imenovanjem (alias). Jedan naziv primijenjen za dva podatka
dovodi do preopterećenja naziva (overload) čija uporaba zahtijeva dodatnu
informaciju za razrješenje dvosmislenosti u dijelovima programa u kojima se
pojavljuje.
Skup vrijednosti podatka i skup operacija definiranih nad tim vrijednostima naziva se
tipom. Za sklopovsku izvedbu tip označava postupak interpretacije bitova podataka
u računalu. Sklopovski tipovi su znak, cijeli broj i realni broj. Oni su u pravilu ugrađeni
i u jezike te se smatraju i osnovnim programskim tipovima. Konstruktor tipa (type
constructor) omogućuje programeru da, uz jezikom definirane tipove, gradi i vlastite.
Tipični konstruktori su pobrajanje, polje, lista, skup, zapis, pokazivač, unija i drugi.
Ograničeni tip ima ograničenu vrijednost (područje vrijednosti). Dva tipa mogu biti
ekvivalentna po nazivu ili strukturno (po skupu vrijednosti i skupu operatora).
Tipizacija je važna za kontekstnu provjeru programa, ali ima situacija u kojima stroga
provjera tipa dovodi do odbacivanja naredbi koje neće proizvesti pogrešan rezultat
(npr. zbrajanje realnog i cijelog broja). Prisila (coercion) je postupak kojim se tijekom
prevođenja provodi pretvorba tipa ovisna o kontekstu da bi se to izbjeglo. Drugo što
se rabi je izravna specifikacija tipova za koje dopušta izjednačavanje (casting).

Stanje

Unutrašnje stanje programa i procesora predočeno je vrijednostima varijabli, a


vanjsko stanje pohranjeno je u ulazno/izlaznim jedinicama.
Promjena stanja provodi se naredbama pridruživanja oblika:

odredište =: izvor

pri čemu odredište može biti sadržano u izvoru koji je konstanta, varijabla ili
izraz. Operatori u izrazu su prefiksni (ispred operanda), infiksni (između operanada)
ili postfiksni (iza operanda). Ulazne naredbe upotrebljavaju vanjsko stanje za
promjenu unutrašnjeg stanja, a izlazne naredbe s pomoću unutrašnjeg mijenjaju
vanjsko stanje.

Tok upravljanja
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 154

Tok upravljanja jednak je redoslijedu izvedbe naredbi koji može biti sljedeći:

• slijed (implicitno sljedeća naredba, eksplicitno skok na označenu naredbu)


• odabir (temeljem provjere uvjeta)
• ponavljanje (u petlji)
• pozivanje (procedura).

Sastav programa

Program je sastavljen od skupina deklaracija podataka i naredbi različite veličine,


namjene i međuovisnosti, kako operativne tako i hijerarhijske. Dijelovima programa
smatraju se blokovi, potprogrami, procesi i moduli.
Blok je skupina deklaracija i naredbi s određenim početkom i završetkom, najčešće
označenim s begin - end, ili drukčije, npr.s{ - }.
Potprogramom se naziva skupina deklaracija i naredbi koja se može pozvati, a
nakon nje nastaviti sa sljedećom naredbom. Potprogrami mogu biti definirani kao:

• procedura, koja ne vraća vrijednost


• funkcija, koja vraća vrijednost
• operator sa smislom funkcije, ali drukčije označen, tj. s oznakom kao što je
imaju npr. aritmetički operatori.

Kad je riječ o povijesno osjetljivim algoritmima (history sensitive) i njihovom


programskom rješavanju ne može se izbjeći pojam stanja i prijelaza između stanja u
smislu konačnog automata (ne stanja programa!). To znači da je potrebno provesti
obradu ovisnu o stanju i primljenoj informaciji (poruka, događaj), a po završetku
obrade odaslati informaciju i promijeniti stanje.
Takav programski konstrukt naziva se često proces kao i u distribuiranim jezicima, a
imaju ga jezici namijenjeni upravljanju primjenom povijesno osjetljivih postupaka.
Procesom se naziva skupina deklaracija i naredbi koja je kao i procedura izvršna
jedinica te se može pozvati, ali za razliku od procedure sadrži pojam stanja. Proces
može biti u različitim stanjima u kojima može primiti različite događaje kao pobudu
za obradu. Procesi se izvode asinkrono, a mogu sadržavati i pozivati procedure.
Svaki proces označava se svojim identifikatorom.
Modulom se naziva više deklaracija tipova, varijabli, rutina, naredbi itd. tretiranih
kao jedna cjelina. U nekim jezicima naziva se paketom (package). Pristup modulu je
ograničen sučeljem što pridonosi skrivanju izvedbenih detalja. Ponovimo da je to
važno za velike programske sustave, a modul je i nastao zbog takvih potreba.
Sličnu zadaću skrivanja unutrašnje strukture ima apstraktni tip podataka (ADT -
Abstract Data Type). Tip je skup vrijednosti i skup operacija za manipulaciju s
vrijednostima. Definiran je samo operacijama za stvaranje i manipulaciju
vrijednostima tog tipa podatka; kad je vrijednost dostupna izravno tada je to
omogućeno isključivo nazivom, a unutrašnja struktura ostaje skrivena od korisnika.
Mnogi jezici nemaju konstrukte za rad s apstraktnim tipovima podataka, ali oni koji
rade s modulima raspolažu mogućnostima apstrakcije i skrivanja informacije. Može se
reći da je razlika između modula i ADT ovakva: modul sadrži definiciju tipa kojom su
deklarirane jedinke i sredstva za manipulaciju tako deklariranim jedinkama, dok je
ADT tip koji se izravno upotrebljava za deklariranje jedinki, a svaka deklarirana
jedinka sadrži sredstva za manipulaciju.
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 155

1.36.2.2 Objektno orijentirani jezici


Osnovna ideja objektno orijentiranih jezika je učahurivanje - skrivanje stanja u objekt
kojem se može pristupiti samo pozivom operacija definiranim nad objektom preko
definiranog sučelja. Objekt čine podaci kao zajedničke unutrašnje varijable i
operacije nad podacima koje se nazivaju metodama. Unutrašnje varijable predočuju
stanje objekta.
Objektni pristup ne odnosi se samo na jezik nego i koncept izgradnje složenih sustava
(veliki i podložni promjenama) kod kojih je najveći problem integracija dijelova u
cjelinu.
Podsjetimo da je klasični pristup velikom sustavu zasnovan na pristupu s vrha prema
dnu (top-down), tako da se dobiva hijerarhijska struktura s funkcijama u prvom planu.
Tako je sustav lakše razviti. Međutim problemi su održavanje njegove promjene i
ponovna uporaba već razvijenih modula. Isto tako različiti se postupci i jezici rabe za
specifikaciju, razvoj i izvedbu što otežava automatsku provjeru i generiranje
programa te je prijeko potreban širok skup pomagala i pravila da bi se postigla
učinkovita proizvodnja kvalitetnih programskih sustava. Ne-objektni modeli su
uglavnom utemeljeni na takvim rješenjima.
Pri objektno orijentiranom pristupu podatak je u prvom planu, a objekti se
upotrebljavaju za "slaganje" sustava, na načelu odozdo prema gore (bottom-up).
Iskustva pokazuju da promjene u specifikaciji manje zadiru u objekte nego u funkcije,
tako da je ranjivost (vulnerability) programskog sustava manja, a ponovna uporaba
(reusability) jednostavnija, jer se u svim razvojnim fazama radi s objektima. Može se
uspostaviti hijerarhija objekata s najopćenitijim objektom na vrhu. Svaki objekt
nasljeđuje operacije od hijerarhijski višeg objekta tako da se postiže ponovna
uporaba operacija.
Načela objektno orijentiranih jezika su nabrojena u nastavku. Obično se smatra da su
učahurivanje podataka, nasljeđivanje i dinamičko povezivanje tri načela bez kojih se
jezik ne može smatrati objektnim.

Učahurivanje podataka

Učahurivanjem podataka (data encapsulation) postiže se da podaci nisu izravno


vidljivi već im se može pristupiti samo putem operacija koje predočuju sučelje objekta
za njegovog korisnika.
Za skrivanje unutrašnje strukture objekta rabe se klase tako da se pri promjeni
formata podataka mijenja samo klasa, a ne dijelovi programa koji rade s njom.

Klasa

Klasa predočuje opis vrste objekta, a sadrži opis unutrašnjih varijabli objekata klase i
operacije koje se mogu primijeniti na objekte klase.
Unutrašnje varijable i operacije nazivaju se članovima klase. Klasa ima neka obilježja
modula, a neka tipa podataka: podatke i operacije združuje kao modul, a slična je
tipu po tome kako se može upotrijebiti za stvaranje nove varijable. Može se reći da
je klasa apstraktni tip podataka koji dodatno omogućuje nasljeđivanje. Varijabla
apstraktnog tipa podataka (tipa klase) naziva se objekt.

Nasljeđivanje
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 156

Nasljeđivanjem (inheritance) se naziva postupak kojim se nova klasa izvodi iz


prethodne, osnovne klase. Pritom izvedena klasa (derived class) nasljeđuje sve članove
klase iz osnovne klase (base class). Uspostavlja se hijerarhija klasa u kojoj svaka klasa
definira samo operatore i varijable specifične za tu klasu, a ostale nasljeđuje od više
klase. Izvedena klasa je "specijalizirana" osnovna klasa.
Za ponovnu iskoristivost programa najvažniji su koncepti klase i nasljeđivanja. Kako
većina jezika ne sadrži uvjete za rad s klasama (preduvjeti za uporabu, uvjeti po
završetku) i ne obvezuju na njihov opis, dokumentacija kao jedino cjelovito rješenje ne
može se izbjeći.

Polimorfizam

Mogućnost dodjele različitog tipa parametrima neke procedure naziva se


polimorfizmom (višeobličnost). Nasljeđivanjem se može ostvariti takve višeoblične
procedure, jer u objektno orijentiranim jezicima, za razliku od imperativnih, tip
parametara nije fiksiran.

Dinamičko povezivanje

Dinamičkim ili kasnim povezivanjem (dynamic binding, late binding) naziva se postupak
kojim se objekt koji izvodi operaciju određuje tijekom izvedbe programa. Dinamičko
povezivanje je fleksibilnije od statičkog koje se provodi tijekom prevođenja, jer ne
dovodi do više varijanti programa ovisnih o tipu i potrebe za ispitivanjem "koja
procedura za koji tip". Ono olakšava održavanje, jer su promjene manje i
lokalizirane, ali produžuje vrijeme izvedbe tako da većina jezika dopušta i statičko i
dinamičko povezivanje.
Primjerice, jezik C++ pretpostavlja statičko povezivanje, a virtualnom funkcijom
označava se da je riječ o dinamičkom povezivanju. Tako se prevoditelju unaprijed
označuje koje će se funkcije povezivati dinamički, a za sve ostale provodi se statičko
povezivanje.
Za telekomunikacije su danas važni objektno orijentirani jezici opće namjene, posebice
Java i C++. Objektno orijentirani jezici se primjenjuju naročito za funkcije upravljanja
mrežom i usluge.

1.36.2.3 Funkcijski jezici


Funkcijski jezici su deklarativni jezici koji se zasnivaju na izračunavanju izraza
sastavljenih od poziva funkcija. Katkad se nazivaju aplikacijskim jezicima.
Funkcijski jezici imaju svojstvo referencijske transparentnosti (referential transparency)
što znači da rezultat pozivanja funkcije ne ovisi o tome kada se funkcija poziva već
kako se poziva, odnosno s kojim argumentima. Nema promjene stanja i nema
popratnih pojava, jer se informacija o stanju prenosi eksplicitno argumentima i
rezultatima, a ne promjenom vrijednosti podatka (sadržaja memorije), kao kod
imperativnih jezika. Još je jedna razlika - imperativni jezici tretiraju različito varijable
(čitaj, piši, mijenjaj) i funkcije (pozivaj), a deklarativni ne.
Kod funkcijskih jezika uvijek vrijedi f(x) + f(x) = 2f(x), za razliku od
imperativnih kod kojih ne možemo biti sigurni da se vrijednost f(x) nije promijenila
između prvog i drugog očitavanja tijekom računanja. Zbog referencijske
transparentnosti olakšana je provjera ispravnosti te optimizacija i paralelizacija
programa što su važni dobici za telekomunikacijske primjene.
Osnovni konstrukt jezika je funkcija koja izračunava vrijednost ovisno o ulaznim
parametrima (argumentima). Npr. za računanje faktorijela:
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 157

factorial(0) ® 1;
factorial(N) ® N * factorial(N-1)

Rekurzivne funkcije zamjenjuju petlju

factorial(N) ® N * factorial(N-1)

Najvažnija struktura podataka je lista

list[1, 2, a, b]

koja pohranjuje promjenjivi broj jedinki (u ovom primjeru 4)

Kao opći nedostaci funkcijskih jezika mogu se navesti sljedeći: nema naredbe
pridruživanja (zamjenjuje se stavljanjem izraza u poziv funkcije), teža je promjena
strukture podataka (postiže se kopiranjem svih elemenata koji se ne mijenjaju i
zamjenom onih koji se mijenjaju), ulazno/izlazne operacije traže posebna rješenja
zbog kršenja referencijske transparentnosti. Posebnosti funkcijskih jezika su dane u
nastavku.

Podudaranje uzorka

Podudaranje uzorka (pattern matching) rabi se za uvjetnu izvedbu funkcije, odnosno


odabir jedne od alternativnih funkcija. Pretpostavimo da se funkcija sastoji od jedne
ili više jednadžbi oblika:

pattern = expression, condition.

Ako je funkcija pozvana, nastoji pronaći takav oblik čiji se uzorak (pattern)
podudara, a uvjet (condition) je ispunjen, odnosno istinit. Uvjet nije obvezatan,
već samo dodatna mogućnost.

Djelomična parametrizacija

Djelomična parametrizacija (currying) dopušta primjenu iste funkcije s različitim


brojem parametara, što praktički znači da se svaka funkcija s više argumenata može
pozvati i sa samo jednim argumentom.

Lijeno izračunavanje

Običnim se izračunavanjem smatra ono kojim se prvo izračuna vrijednost izraza u


funkcijskom pozivu, a zatim pozove funkcija.
Ako je riječ o složenom izrazu, redukcija izraza radi se aplikacijskim poretkom
(application order reduction) tako da se prvo izračunavaju unutrašnje funkcije u izrazu
pa se zatim primjenjuje vanjska. Normalni poredak (normal order reduction) poštuje
redoslijed kojim su funkcije navedene.
Aplikacijski poredak koji se rabi u imperativnim jezicima nije dobar za funkcijske
jezike. Naime, argumenti se izračunavaju uvijek, neovisno o tome da li su potrebni ili
ne (npr. uvjetni poziv funkcije).
Kod normalnog se poretka može vrijednost funkcije izračunavati samo kad je to
potrebno. To se naziva lijenim izračunavanjem (lazy evaluation), a za jezik s lijenim
izračunavanjem se kaže da nema striktnu semantiku (strict semantics). Lijeno
izračunavanje pomaže rješavanju ulazno/izlaznih operacija kod funkcijskih jezika.
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 158

1.36.2.4 Logički jezici


Logički jezici su primjer problemski usmjerenih jezika - činjenice i obilježja problema
su u središtu pozornosti za razliku od imperativnih kod kojih je najvažnije upravljanje,
objektnih jezika s težištem na podatku, a funkcijskih na opisu problema skupom
funkcija.
Temeljni formalizam je Hornova logika tako da se logički program gradi od pravila
nazvanih Hornova rečenica (clause) koja govori da ako (if) je neki uvjet ispunjen -
istinit tada (then) ni jedan, jedan ili više uvjeta biva istinitima. Činjenica je definirana
kao Hornova rečenica bez uvjeta. Logički program je skup pravila oblika ako - tada
(if - then). Hornova rečenica označit će se sa:

G0 → G1, G2,..., Gn.

Ako su G1, G2,..., Gn istiniti tada je i G0 istinit. G0 se naziva zaglavljem


(head), a G1, G2, .....Gn čine tijelo (body) rečenice. G0, G1, G2,
.....Gn su ciljevi (goal), a G1, G2, .....Gn podciljevi (subgoals). Svaki cilj
sastoji se od naziva predikata te nijednog, jednog ili više argumenata od kojih svaki
može biti varijabla ili konstanta što se piše ovako:

predikat (konstanta)
predikat (Varijabla).

Primjerom ćemo pokazati kako se tvore i izvode Hornove rečenice. Početni cilj
predočen je upitom "Da li je Ivan sretan? ":

? ® sretan (ivan)

i skupom pravila za rješavanje:

sretan (X) ® bogat (X), poznat (X)


sretan (X) ® mlad (X), zaljubljen (X)
sretan (X) ® star (X), mudar (X).

Pravila se provjeravaju u proizvoljnom redoslijedu (nedeterministički), a ispunjenost


jednoga dovoljna je za dokazivanje početni cilj. Kod prvog pravila nastoji se
dokazati oba podcilja bogat (x) i poznat (x). Kad god se ne uspije dokazati
neki podcilj prelazi se na drugi cilj. Taj postupak naziva se ponovno pretraživanje
(backtracking).
Logičke varijable (X u ovom primjeru) omogućuju pohranjivanje informacije o stanju i
prijenos argumenata između ciljeva.
Činjenica se označuje rečenicom kakva je primjerice:

mudar (sokrat).

Logički se može definirati poslove obrade poziva, npr. da "korisniku X treba zvoniti
telefon ako je pozvan i ako je slobodan":

zvoni (X) ® pozvan (X), slobodan (X)


PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 159

1.36.2.5 Odnos deklarativnih jezika prema objektno orijentiranom


programiranju
Glede deklarativnih jezika, komentirat će se odnos prema objektno orijentiranom
programiranju i objektno orijentiranom razvoju, i to na primjeru jezika Erlang:

• apstrakcija: postiže se uporabom modula kojim se definiraju funkcije sa značenjem


sučelja preko kojih se provode operacije nad nespecificiranim tipovima.
• objekt: prikazuje se procesom ili eksplicitnom strukturom podataka. Proces
zadovoljava sve kriterije za objekt (jednoznačna identifikacija, zatvoreni podaci,
apstrakcija ostvarena sučeljem, postojanje određeno programskim kodom koji
izvodi). Struktura podataka je neizmjenjiva, tako da ju ne treba štititi od
neautoriziranog pristupa; učahurivanje podataka i funkcija postiže se uporabom
modula.
• nasljeđivanje: klasa se izvodi modulom, proces je instanca klase, a komunikacija s
objektom rješava se izmjenom informacije s pripadnim procesom. Postoje funkcije
za određivanje odnosa osnovne i izvedene klase i izračunavanja u njima.

Autori jezika Erlang smatraju da je bolje da jezik ne sadrži izravne konstrukte za


klase i nasljeđivanje već da samo "pokriva" objektno orijentirani koncept, jer to daje
veće mogućnosti prilagođavanja pojedinoj primjeni. Isto tako, centralizirana izmjena
informacija karakteristična za objektno orijentirane sustave, neprihvatljiva je za
povijesno osjetljive primjene (stanje - prijelaz) i distribuirana rješenja, jer skriva tko
izmjenjuje informacije. Dakako, stoga i neke odlike objektno orijentiranog
programiranja ostaju skrivene.
Sljedeći je problem odnos objektno orijentirane metodologije i konkurentnog jezika.
Identifikacija objekata središnji je korak u objektnoj metodologiji, a za primjene u
kojima je postojanje paralelnih aktivnosti očito, konkurentni objekti su nužnost. Jezik
Erlang osigurava prave konkurentne objekte i komunikaciju između njih te omogućuje
preslikavanje jedan prema jedan između objekata i procesa.

4.2. OTVORENE PLATFORME


4.2.1. Generički skup funkcija telekomunikacijskog sustava
Generički skup funkcija telekomunikacijskog sustava obuhvaća tri skupine međusobno
surađujućih funkcija:

F1 – Operacije s informacijskim tokovima


F2 – Inteligencija obrade poziva i usluga
F3 – Upravljanje čvorom i mrežom

Funkcijska skupina F1 ostvaruje fizikalno sučelje s informacijskim tokom (električko,


optičko, …) te se izvodi sklopovskim jedinicama, u složenijim slučajevima s ugrađenim
procesorskim elementima. Funkcijske skupine F2 i F3 različitih su informacijskih
karakteristika (složenost, učestalost uporabe, vremenski uvjeti, …), a obje se izvode
programski, jer sve informacije prikupljaju ili usmjeravaju putem funkcijske skupine F1
(slika 5.1)
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 160

Slika 4.1 – Generičke funkcije

Promatrajući neku funkciju moguće je pretpostaviti sljedeće načine rješavanja:

• dijelovi funkcije fi nalaze se u sve tri skupine: fi1 – programski upravljivi i ispitljivi
sklopovski dio (npr. linijska jedinica za priključak korisnika), fi2 – programski dio
za kontrolu poziva i usluge (npr. analiza biranog broja), fi3 – programski dio za
upravljanje sustavom ili mrežom (npr. određivanje putova kroz mrežu),
• dijelovi funkcije fj nalaze se u dvije funkcijske skupine: fj2 – programski dio za
kontrolu poziva i usluge (npr. brojanje naplatnih impulsa), fi3 – programski dio za
upravljanje sustavom ili mrežom (npr. postavljanje cijene naplatnog impulsa),
• dijelovi funkcije fk nalaze se u jednoj skupini: – programski dio za upravljanje
sustavom ili mrežom (npr. analiza kvarova i preusmjeravanje prometa).

Glede izgradnje sustava razlikuju se dva pristupa: vlasnički (proprietary) i otvoreni


(open).
U vlasničkom pristupu se sve razine i svi dijelovi funkcija izvode u cijelosti za
razmatrani sustav, npr. posebnim sklopovskim jedinicama, programima pisanim u
posebnom jeziku koji se izvodi na posebno izvedenim procesorima, s posebnim
operacijskim sustavom. Kod otvorenog pristupa se sve razine i svi dijelovi funkcija
izgrađuju na temelju općih rješenja, kako za sklopovske jedinice tako i programske
jezike, operacijski sustav i procesor.
Vlasnički pristup karakterizirao je početke procesorskog upravljanja u
telekomunikacijama, jer se u to doba (60tih i 70tih godina prošlog stoljeća)
zahtijevane performanse sustava nisu mogle postići općim rješenjima jezika,
operacijskih sustava i procesora. Nadalje, tako se bolje štitila konkurentnost
proizvoda, a zahtjevi tržišta s obzirom na trajanje razvoja, odnosno izlaska
proizvoda na tržište (TTM - Time To Market) nisu bile preoštri. Otvoreni pristup javlja
se u novim okolnostima (90tih godina prošlog stoljeća) kad opća rješenja postaju
dovoljno kvalitetna, a tržišni zahtjevi, posebice na trajanje razvoja, bitno zaoštreni.
Otvoreni pristup pretpostavlja da svaki proizvođač ne radi sve, već se u većoj mjeri
oslanja na gotove programske i procesorske komponente i rješenja, slično kako je to
uobičajeno u sklopovlju. Svaki složeni sustav u pravilu objedinjuje oba pristupa, da bi
iskoristio sve prednosti koje nose. „Miješaju“ se oba pristupa, s većim ili manjim
udjelom pojedinoga.
U vlasničkom pristupu programski se proizvod tipično izvodi kroz sve ili više funkcijskih
razina, tako da se proteže vertikalno kroz strukturu sustava (slika 5.2) i sadrži sve
posebno izvedene dijelove. Neki od njih mogu biti zajednički za više proizvoda (npr.
operacijski sustav), a onaj dio koji definira funkcionalnost proizvoda uvijek je posebno
izveden.
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 161

fi fj fk

F3

F2

F1 Razvijeni dijelovi

Slika 4.2 – Vertikalno ustrojstvo

U otvorenom pristupu programski proizvod se nastoji izvesti sa što više “gotovih”


dijelova (općih ili prethodno napravljenih pa ponovno uporabljenih). Takvi dijelovi se
horizontalno slažu jedan na drugi i međusobno povezuju. Horizontalni proizvod često
je sveden je na dio koji definira posebnu funkcionalnost, a svi ostali dijelovi su
standardni (slika 5.3).

fi fj fk

Razvijeni dijelovi
Standardni dijelovi

Slika 4.3 – Horizontalno ustrojstvo

4.2.2. Otvoreno procesiranje


Otvoreno procesiranje je koncept koji opisuje otvoreni pristup strukturiranju
programskih proizvoda. Za početak, razmotrit će se odnos procesora, operacijskog
sustava i programa - aplikacije. Teorijski, otvorenost bi značila program ne zavisi o
procesoru iz definiranog skupa procesora i operacijskom sustavu iz definiranog skupa
operacijskih sustava, tj. da se može izvoditi na različitim sustavskim platformama. To
se praktički može postići izolacijom programa od operacijskog sustava i izvedbom
operacijskog sustava čiji je samo jezgreni dio (kernel) procesorski ovisan. Neovisnost
se postiže virtualnim procesorom - strojem (virtual machine) koji izvodi program, a
kojim se rješava prilagodba pojedinom operacijskom sustavu, odnosno procesoru
(slika 5.4). Programski jezici kod kojih se želi postići visoka razina prenosivosti i
neovisnosti o sustavu na kojem se izvode zasnivaju se na virtualnim strojevima, npr.
Java i Erlang.
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 162

V
i
r s
Prevoditelj t t
u r
Biblioteka a o
l j
n
Jezgra i

Operacijski sustav

Jezgra

Procesor

Slika 4.4 – Virtualni stroj

Virtualni stroj u pravilu sadrži jezgru koja pokreće izvođenje programa, biblioteku s
gotovim rješenjima koja se mogu upotrijebiti pri izradi programa, te prevoditelj za
programski jezik.
Virtualni stroj se izvodi kao proces operacijskog sustava. Na istom procesoru mogu se
izvoditi “obični” procesi konkurentno s virtualnim strojevima, a moguće su situacije u
kojima se na istom stvarnom procesoru ustrojava i izvodi više virtualnih procesora.

Primjer 1

Virtualni stroj za jezik Java omogućuje izvedbu programa nad različitim operacijskim
sustavima. Virtualni stroj platforme .NET omogućuje izvedbu različitih programskih
jezika (više od 30) nad operacijskim sustavom Windows.

Primjer 2

Operacijski sustav i virtualni stroj pružaju potporu primjenama kao što su pristup
različitim vanjskim jedinicama i mreži, grafika, baza podataka i sl. Isto tako, ukoliko
se želi omogućiti uporaba različitih jezika u istom programskom proizvodu (dijelovi
izvedeni različitim jezicima), to treba riješiti sustavskom potporom (slika 5.5).
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 163

Program (Jezik 2) Program (Jezik 1)

Sustavska
Sustavska potpora
potpora

Virtualni stroj

Operacijski sustav

Slika 4.5 – Programski dijelovi u različitim jezicima

Rješenje za neovisne programske dijelove bilo bi jednostavno, jer ne bi trebalo


ostvariti međuprocesnu komunikaciju, npr. izmjenom poruka. Međuprocesna
komunikacija između dijelova napisanih u različitim jezicima, kao općenito rješenje,
zahtijeva dodatne mehanizme.
Neka se kao primjer razmotri suradnja programskih dijelova izvedenih u objektno
orijentiranom jeziku i funkcijskom jeziku s tipovima podataka i procesnom strukturom
(slika 5.6).

Objektna Funkcijska
aplikacija aplikacija

Pokretanje procesa
Klase za komunikaciju Izmjena poruka
Klase za tipove Pozivanje funkcija

Objektni jezik Funkcijski jezik


Slika 4.6 – Komunikacija programskih dijelova izvedenih u različitim jezicima

Na strani objektno orijentiranog jezika potrebno je uvesti klase za komunikaciju i


tipove kojima će se ostvariti izmjena poruka. Na strani funkcijskog jezika treba riješiti
pokretanje procesa, pozivanje funkcija i naravno, izmjenu poruka (slika 5.6).

Primjer 3

Neposredno upravljanje (sklopovskim jedinicama) najčešće se zasniva na primarnom


intervalu obrade (npr. od nekoliko ms) u kojem se kružno (ciklički) izvode svi programi
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 164

kojima se ostvaruje međudjelovanje sa sklopovskim jedinicama. Pritom primarni


interval treba biti dovoljno dugačak (odnosno programi dovoljno kratkog trajanja) da
bi se mogle izvoditi i funkcije održavanja (testiranje priključenih sklopovskih jedinica i
samih procesora). Kružni slijed obrade može se prekinuti i umetnuti izvođenje
dodatnog, posebno zahtijevanog programa.
Složenije su prilike na razini globalnog upravljanja na kojoj je izraženija
međuovisnost funkcija, a samim time i postupci raspoređivanja programa za izvođenje
složeniji (slika 5.7). Obrada prometa u mrežnim čvorovima traži sljedeće mogućnosti:

• periodičko aktiviranje programa,


• aktiviranje programa u zadano vrijeme,
• aktiviranje programa sa zadanim kašnjenjem,
• izvođenje programa na temelju logike odvijanja poziva i usluga (tj. zahtjeva
nekog drugog programa),
• utvrđivanje i mijenjanje prioriteta programa.

Sustavski dio koji omogućuje izvedbu programa naziva se izvršna programska


podrška (IPP). Njezine funkcije su:

• prikupljanje zahtjeva za obradom iz programa koji se izvode u samom


procesoru ili u drugim procesorima i njihovo pohranjivanje u spremnik poslova,
• održavanje repova za vremensko aktiviranje programa (periodički, s
apsolutnim ili relativnim kašnjenjem),
• odražavanje spremnika poslova po prioritetima, a unutar svakog prioriteta po
redoslijedu nailaska,
• odabir programa za izvođenje.

Čak i ovakav vrlo pojednostavljeni prikaz ukazuje na značajke koji bi trebali imati
programski jezik i programski sustav (izvršna i aplikacijska programska podrška) da
bi bio prilagođen telekomunikacijskom procesiranju. To su za programski jezik:

• mogućnost prikaza procesa poziva i usluga skupom slabo povezanih


programskih modula,
• definiranje programskog modula kao programa s vlastitim podacima,
• međudjelovanje s okolinom koje omogućuje prijam informacije iz te predaju
informacije u okolinu,
• međudjelovanje programskih modula - pokretanje jednog modula iz drugog
(nakon drugog) uz dostavu podataka (po potrebi),
• istodobna izvedaba više programskih modula.
PROGRAMIRANJE TELEKOMUNIKACIJSKIH FUNKCIJA 165

n-1
IPP PROG 1 IPP
Ciklus Spremnik
poslova
0
PROG
Apsolutno
vrijeme
PROG x
PROG y
PROG
Relativno
vrijeme

drugi
procesor

PROG z

Slika 4.7 - Izvršna programska podrška

U programu se može ostvariti jedno ili više međudjelovanja s okolinom koja ga


pokreću. Povezivanje programskih modula ostvaruje se tako da se povežu izlaz
jednog s ulazom drugog (po završetku prvog mora se izvesti drugi). Ako programski
modul koji je sljedeći u izvođenju treba podatak (podatke) od svog prethodnika taj
mu ih može dostaviti tijekom povezivanja.
Ako za to vrijeme nije tražena obrada obavljena do kraja, zahtjev se unutrašnjim
povratnim vezama vraća u sustav repova i čeka svoj idući red, kada će mu biti opet
dodijeljeno vrijeme i sredstva za obradu. Taj se postupak ponavlja dok se tražena
obrada ne obavi do kraja, nakon čega zahtjev izlazi iz sustava za obradu kao
završeni posao. Sa stajališta informacijske analize ključni parametar koji će opisivati
svojstva sustava za obradu bit će vrijeme od trenutka ulaska zahtjeva u sustav pa do
završetka posla, odnosno vrijeme odgovora. Različite organizacije sustava za obradu
različito će se ponašati s obzirom na spomenuti parametar.
LITERATURA 166

8. LITERATURA

1. Bal, H. E., D. Grune, „Programming Language Essentials“, Adison-Wesley, 1994.


2. Wegner, P., „Concepts and Paradigms of Object Oriented Languages“, OOPS
Messenger, ACM Press, Vol. 1, No. 1, 1990.
3. Armstrong, J.A., Virding, R.V., Wilkstrom, C.W., Williams, M.W., „Concurrent
Programming in ERLANG“, Second edition, Prentice Hall Europe, 1996.
4. Armstrong, J.A., „Programming Erlang: Software for a Concurrent World“, The
Pragmatic Bookshelf, 2007.
5. http://www.erlang.org (2009.)
6. http://erlide.sourceforge.net (2009.)
7. Mary Campione, Kathy Walrath, The Java Tutorial, Addison-Wesley, 1996.,
[On-Line] http://java.sun.com/docs/books/tutorial/
8. D. Lea: Concurrent Programming in Java - Design Principles and Patterns,
Addison Wesley, 1997.
9. Jeff Friesen, Achieve strong performance with threads, JavaWorld, 2002.,
http://www.javaworld.com/javaworld/jw-05-2002/jw-0503-java101.html
10. Allen Holub, Programming Java threads in the real world, JavaWorld, 1998.,
http://www.javaworld.com/javaworld/jw-09-1998/jw-09-threads.html
11. IBM, Concurrency in JDK 5.0, 2004, IBM developerWorks,
http://www.ibm.com/developerworks/edu/j-dw-java-concur-i.html
12. Brian Goetz, Java theory and practice: To mutate or not to mutate?, 2003., IBM
developerWorks, http:// www-106.ibm.com/developerworks/java/library/j-
jtp02183.html
13. Brian Goetz, Java theory and practice: More flexible, scalable locking in JDK
5.0, 2004., IBM developerWorks, http://www-
106.ibm.com/developerworks/java/library/j-jtp10264
14. John Zukowski, Taming Tiger: Concurrent collections, 2004., IBM developerWorks,
http://www-106.ibm.com/developerworks/java/library/j-tiger06164.html
15. Rodger Lea, Christian Jacquemot, Eric Pillevesse, COOL: system support for
distributed programming, Communcations of the ACM, vol. 36, no. 9, 1993., str.
37-46, ACM Press
16. Bruno Achauer, The DOWL distributed object-oriented language, Communcations
of the ACM, vol. 36, no. 9, 1993., str. 48-55, ACM Press
17. Bertrand Meyer, Systematic concurrent object-oriented programming,
Communcations of the ACM, vol. 36, no. 9, 1993., str. 56-80, ACM Press
18. Klaus-Peter Lohr, Concurrency annotations for reusable software, Communcations
of the ACM, vol. 36, no. 9, 1993., str. 81-89, ACM Press
19. Denis Caromel, Toward a method of object-oriented concurrent programming,
Communcations of the ACM, vol. 36, no. 9, 1993., str. 90-102, ACM Press
20. Nick Benton, Luca Cardelli, Cedric Fournet, Modern concurrency abstractions for
C#, ACM Transactions on Programing Languages and Systems, vol. 26, no. 5,
2004., ACM Press

You might also like