Professional Documents
Culture Documents
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. 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
• stabilizacija jezika
• Cobol: jezik s najviše programera i primjena
• Lisp: najvažniji jezik za umjetnu inteligenciju
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.
1990. – 1999.
2000. – do danas
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.
Promjena stanja
Distribucija poruka
Klasifikacija
Objekt
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)).
Kod jezika za industrijske primjene nastoji se popraviti čitljivost koja je jako važna za
održavanje i evoluciju programskih proizvoda.
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.
U svakom komunikacijskom sustavu mogu se ustanoviti tri generičke skupine funkcija (sl.
3.1):
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
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).
memorija
programa
TOČKA
UPRAVLJANJA
programsko instrukcija i
brojilo instrukcija j
instrukcija k
memorija
podataka
podatak a
podatak b ADRESA
podatak c
Skillicornova metodologija zanimljiva je, jer uvodi apstraktne modele slične onima koji
se primjenjuju u procesorskom upravljanju telekomunikacijskim sustavima. Funkcijske
jedinice apstraktnog procesora su:
• adresa instrukcije,
• instrukcija,
• adresa podatka
• podatak (operand, rezultat) i
• stanje operacije.
3
DP 8 IP
4 5
1 2
6 7
DM IM
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
IM IM
DP DP DP
a) Operacije s instrukcijama
IP IP DM
prima DM
podatak
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
SW
nxn DP IP
DM IM
SW
nxn DP
DM
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
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
1.7.1 Korutine
prog. 1
prog. 2
parbegin
1
2
3 istodobno
4
parend
1 2 3 4
1.7.3 Proces
naredbe
podaci
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)
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
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.
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).
Proces S Proces R
send to R receive from S
a) simetrično izravno
Proces S'
send to R Proces R
send to R
b) asimetrično izravno
Proces S'
send to P Proces R
send to P
c) posredno imenovanje
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.
U slanje poruke tada treba uključiti identifikaciju predajnog procesa funkcijom self():
Pid!{self(),abc},
kao i u prijam:
receive
{From, Message} ->
Actions;
end.
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
1:m
n:m
• svaki proces može poslati poruku svakom procesu, svaki proces može primiti
poruku od svakog procesa,
Sinkrona komunikacija
Komunikacija je sinkrona ako predajni proces nastavlja obradu tek nakon potvrde od
prijamnog procesa (sl. 4.9).
S R
send
poruka
receive
potvrda
nastavlja
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).
S R
send
odmah M
nastavlja
receive
Eksplicitni je prijam onaj kod kojeg prijamni proces kontrolira i određuje kad će
primiti poruku (sl. 4.11).
R
čeka
poruku
receive
nastavlja
poruka
<<t
nastavlja
receive
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
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.
klijent(Zahtjev)->
server1!{self(), Zahtjev1},
server2!{self(), Zahtjev2},
receive
{server1, Odgovor} ->
R1;
{server2, Odgovor} ->
R2
end.
X Y
zahtjev
čeka
obrada
rezultat
X1 Y
upit
prima
čeka poruku
odgovor
obrada
X1 upit
odgovor
čeka
Jezik Erlang u raspodijeljenoj primjeni (više procesora ili mreža) radi s udaljenim
pozivom procedura.
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.
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.
receive
Message1 [when Guard1] ->
Actions1 ;
Message2 [when Guard2] ->
Actions2 ;
...
after
TimeOutExpr ->
ActionT ;
end.
sleep(Time)
receive
after Time ->
true
end.
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
Konkurentnost
Stvarno vrijeme
Non-stop sustav
Robustnost
Upravljanje memorijom
Raspodijeljenost
Erlang ne koristi zajedničku memoriju već svu komunikaciju između procesa obavlja
asinkronom izmjenom poruka.
Erlang ima ugrađenu vezu prema drugim programskim jezicima kao npr. C, Java i sl.
Platforme
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.
factorial(0) -> 1;
factorial(N) -> N * factorial(N-1).
ne rabi se u Erlangu.
Logika
Vrste podataka
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:
Pridruživanje
pattern = expression
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
• osnovne vrste,
• izvedene vrste.
Brojevi
Brojevi predstavljaju cijele i realne brojeve i mogu imati sljedeći oblik: 455, -345,
4.454345, 7.8e22, -1.2e-22.
Atomi
N-torke
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“.
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
Zapisi
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
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
Primjer:
Primjer:
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
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:
-module(ModuleName)
-export(FunctionName).
Da bi modul mogao pozvati funkciju iz drugog modula moramo ga uvesti kao što
slijedi:
-import(ModuleName, FunctionName).
-module(prvi).
-export([poruka/0]).
%% Ovo je komentar
poruka() ->
io:format(“Hello world !~n”).
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]).
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
factoriel(N) ->
N*factoriel(N-1);
factoriel(0) -> 1.
Primjeri uvjeta:
case izraz of
uzorak1 ->
naredbe1;
uzorak2 ->
naredbe2;
....
end,
if
uvjet1 ->
naredbe1;
uvjet2 ->
naredbe2
end,
Primjeri:
-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).
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).
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.
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).
PID!Poruka
receive
{poruka}->
akcija...
end.
Primjer:
{A_PID, poruka}
A B
B_PID!{self(),poruka} receive
{From,Msg}->
naredbe…
end.
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
{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.
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.
C!poruka1 C
B receive
poruka1->
akcija1…
C!poruka2 poruka2->
akcija2…
end.
C!poruka1 C
B receive
Poruka->
akcija…
C!poruka2 end.
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.
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
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).
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.
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
server(Data) ->
receive
{From,{request,X}} ->
{R,Data1}=fn(X,Data),
From!{myserver,{reply,R}},
server(Data1)
end.
request(Req)->
myserver!{self(),{request,Req}},
receive
{myserver,{reply,Rep}} ->
R
end.
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.
receive
poruka1 ->
akcija1;
..........
after
Time ->
akcija2
end.
sleep(T)->
receive
after T ->
true
end.
suspend() ->
receive
after infinity ->
true
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
-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]
Finish
ok
2>
Brzina
Neki od udaljenih čvorova mogu imati ugrađeni specijalizirani hardver koji će na ovaj
način postati dostupan svima ostalim.
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.
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:
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
Parametar –name omogućuje unos naziva čvora. Ako se parametar izostavi čvor
dobija unaprijed zadano ime (nonode@nohost).
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
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.
…
monitor_node(Node, true)
receive
{nodedown,Node}->
io:format("Čvor ~w nedostupan! ~n",[Node]);
after 3000 ->
idemo_dalje
end,
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.
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:
Proces Pid1
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
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.
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.
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).
2> prvi:test(1,x).
{ok,x}
3> prvi:test(2,x).
** exception throw: {myerror,x}
in function prvi:test/2
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
6> prvi:test(5,x).
{'EXIT',x}
7> prvi:test(6,x).
** exception error: x
in function prvi:test/2
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 57
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.
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}]
try F of
Val -> Val
catch
…
end
try F
catch
…
end
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 58
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.
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}.
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.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
statistics(run_queue)
Proces Pid2
-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.
normal:start(3).
kreiraju se tri procesa, od kojih je prvi registriran imenom start (sl. 5.11).
normal:test(321).
normal:test(stop).
Proces 2 primio stop
Proces 1 primio stop
Posljednji proces zavrsava
stop
signal EXIT
signal EXIT
proces start
završava
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.
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!").
c:c(naziv_modula).
1> c(prvi).
{ok,prvi}
naziv_modula:naziv_funkcije(Popis_parametara).
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$
erlc prvi.erl
DEKLARATIVNI KONKURENTNI JEZIK ERLANG 64
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
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
%%
zajednička memorija
Legenda:
P P P ... P procesor P
proces
nit
• 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.
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, ...
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 {
extends Thread {
public ThreadExample(String name) {
super(name);
}
alive
- sleep()
- suspend() / resume()
new - wait() / notify()
yield()
- kritični odsječak
start()
New Not
Runnable
thread runnable
dead
• 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.
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.
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.
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.
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.
public Point() {
}
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;
setPoint(13,13)
x=13
setPoint(4,4)
x=4
y=4
toString()
return "(4,4)"
y=13
toString()
return "(4,13)"
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.
izvodi zaustavi
izvodi kod nit
kod
odključaj
objekt
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() {
}
setPoint(11,11)
x=11
y=11
setPoint(1,1)
x=1
y=1
toString()
return "(1,1)"
toString()
return "(1,1)"
Kod ispravno radi, ali je ispis krivi jer se između zapisivanja i čitanja podatka
(metoda setPoint i toString) zapisala drugu vrijednost.
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.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;
}
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.
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
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
get()
wait()
put(0)
num=1
bEmpty=false
notifyAll()
put(1)
wait()
bEmpty=true
notifyAll()
0
list.add( "drugi" );
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;
list.add( "prvi" );
list.add( "drugi" );
@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;
@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);
Thread.sleep( 3000 );
Uzeo: 7
Stavio: 8
Uzeo: 8
Stavio: 9
Uzeo: 9
Exchanger<Integer> 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;
@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
{
nit2: 3
nit2: 4
nit1: nakon nastavka
nit1: 3
nit1: 4
temp = num;
condition = numberGot;
numberGot.signal();
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
return temp;
}
{
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.
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
{
ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 10,
10, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(3));
BlockingQueue<Runnable> queue = pool.getQueue();
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
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;
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
public WrongWay()
{
pbProgress.setMaximum( 10 );
pbProgress.setMinimum( 0 );
try
{
jbInit();
}
catch ( Exception e )
{
e.printStackTrace();
}
}
Thread.sleep( 500 );
}
catch ( InterruptedException ie )
{
}
}
}
public CorrectWay()
{
pbProgress.setMaximum( 10 );
pbProgress.setMinimum( 0 );
try
{
jbInit();
}
catch ( Exception e )
{
e.printStackTrace();
}
}
}
} );
this.getContentPane().add( btnAction, BorderLayout.WEST );
this.getContentPane().add( pbProgress, BorderLayout.CENTER
);
}
gui.setProgressBarValue( 0 );
for ( int i = 0; i <= 10; i++ )
{
SwingUtilities.invokeLater( new SetValue( i, gui ) );
try
{
Thread.sleep( 500 );
}
catch ( InterruptedException ie )
{
}
}
BufferedReader brStdIn =
new BufferedReader( new InputStreamReader(
System.in ) );
String strUserInput;
out.close();
in.close();
}
catch ( IOException ioe )
{
}
finally
{
try
{
socket.close();
}
catch ( Exception e )
{
}
}
}
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;
out.close();
in.close();
}
catch ( IOException ioe )
{
}
finally
{
try
{
socket.close();
}
catch ( Exception e )
{
}
}
}
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
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
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].
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.
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;
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();
}
}
public Communication() {
in = System.in;
out = System.out;
closeConnection = 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;
}
}
}
return decodeBuffer(buffer);
}
in.read( buffer );
return buffer;
}
POVEZIVANJE PROGRAMSKIH JEZIKA JAVA I ERLANG 121
this.num2 = num2;
}
start() ->
spawn(
fun() ->
register(my_tcp, self()),
process_flag(trap_exit, true),
loop()
end).
stop() ->
my_tcp ! stop.
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.
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();
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().
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).
mbox.registerNeme( "jMBox" );
Ako želimo ovo stvoriti sandučić i odmah ga registrirati pod određenim imenom onda
koristimo sljedeću naredbu.
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.
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.
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:
receive
Result ->
% obrada poruke
end,
while ( true ) {
OtpErlangObject obj = mbox.receive();
OtpErlangAtom function =
(OtpErlangAtom) tuple2.elementAt( 0 );
OtpErlangLong num1 =
(OtpErlangLong) tuple2.elementAt( 1 );
OtpErlangLong num2 =
(OtpErlangLong) tuple2.elementAt( 2 );
> 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
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
2
1
z2
1 2 t
Slika 2.3 - "ILI" logika izvođenja
i = max
k
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:
Pri tome f(ti, t) pokazuje aktivnost čvora i (odnosno posla zi) tijekom vremena. F(t, t)
pokazuje ukupnu aktivnost grafa kao funkciju vremena.
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 =
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
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.
Primjer: Za zadani graf poslova odrediti minimalni broj procesora na koje treba
rasporediti poslove uz najkraće vrijeme izvođenja.
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
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
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
• procesorski sustav,
• skup poslova,
• trošak izvođenja i komunikacije (najčešće se izražava trajanjem),
• raspored i
• mjeru uspješnosti.
gdje je:
Pij = 1
Pi Pj
Rij
Si, Ii, Bi Sj, Ij, Bj
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.
gdje je:
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
Tij = Ai/Sj + Bj
Komunikacijsko kašnjenje između poslova zi1 i zi2 kad se izvode na procesorima Pj1 i
Pj2 iznosi:
Ako se šalje više poruka po istoj vezi mora se uračunati dodatno kašnjenje CDj1j2 koje
odgovara trajanju prijenosa poruke:
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:
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).
A2 = 10
z1 z2
1
A1 = 10
1
A3 = 20
z4
z3 A4 = 20
D15 = 1
1
1
z5 A5 = 5
D35
P2 z2 z3
P1 z1 z4 z5
10 20 30 36 t
D24
• 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
z4 t4,v4 z5 t5,v5 z6
e47
e67 t6,v6
c47 e57 c57 c67
z7 t7,v7
a uz svaku granu nalazi se parametar: vrijeme komunikacije cij između poslova zi i zj.
Pl zj
Pk zi
C eij
t
tx tx+ti tx+ti+cij tx+ti+cij+tj
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
z4 t4,v4 z5 t5,v5 z6
c67
e47 e57 c57 e67
z7 t7,v7
visina 0 1 2 3
P1 z1 z3 z5 z7 1
lista
raspored A
1 2 2
P2 lista 2
z2 z4 z6
FV(S)=Cmax-ft(Pj).
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
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
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.
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:
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
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
Podatak
Stanje
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:
Sastav programa
Učahurivanje podataka
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
Polimorfizam
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.
factorial(0) ® 1;
factorial(N) ® N * factorial(N-1)
factorial(N) ® N * factorial(N-1)
list[1, 2, a, b]
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
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
Lijeno izračunavanje
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)
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":
• 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).
fi fj fk
F3
F2
F1 Razvijeni dijelovi
fi fj fk
Razvijeni dijelovi
Standardni dijelovi
V
i
r s
Prevoditelj t t
u r
Biblioteka a o
l j
n
Jezgra i
Operacijski sustav
Jezgra
Procesor
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
Sustavska
Sustavska potpora
potpora
Virtualni stroj
Operacijski sustav
Objektna Funkcijska
aplikacija aplikacija
Pokretanje procesa
Klase za komunikaciju Izmjena poruka
Klase za tipove Pozivanje funkcija
Primjer 3
Č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:
n-1
IPP PROG 1 IPP
Ciklus Spremnik
poslova
0
PROG
Apsolutno
vrijeme
PROG x
PROG y
PROG
Relativno
vrijeme
drugi
procesor
PROG z
8. LITERATURA