Višenitno programiranje (2) - Dragoljub Pilipović
CET Čitalište 72 – April 2009.
Višenitno programiranje u jezicima Java i C# (2) Prioriteti niti u Javi Kada računar ima jedan procesor, izvršava se samo jedna nit u nekom trenutku ali tako da spolja izgleda konkurentno. Izvršavanje više niti na jednom jedinom procesoru po nekom redosledu naziva se scheduling (raspoređivanje). Java podržava prost algoritam raspoređivanja koji se zove fiksiran raspored prioriteta. Ovaj algoritam raspore đuje niti po njihovom prioritetu u odnosu na druge niti. Pravilo je da se u bilo kom trenutku izvršava nit najvišeg prioriteta, jer se uvek bira ona nit koja ima najviši prioritet. Samo kada je nit stopirana ili je suspendovana, po činje da se izvršava nit manjeg prioriteta. Ako dve niti istog prioriteta čekaju na procesor, raspoređivač bira jednu slu čajnim izborom. Kada se kreira neka nit, ona nasleđuje prioritet niti iz koje je izvedena. Prioritet niti se može promeniti nakon kreiranja korišćenjem metode setPriority(). Prioritet niti nalazi se u intervalu MIN_PRIORITY i MAX_PRIORITY. Najmanji mogući prioritet niti u Javi je 1, maksimalni 10, a podrazumevani dodeljen osnovnoj niti 5 (NORM_PRIORITY):
prva_nit.setPriority(Thread.MIN_PRIORITY); //isto kao prva_nit.setPriority(0); druga_nit.setPriority(druga_nit.getPriority()+1); //povecaj za jedan prioritet druge niti Ono što predstavlja potencijalni problem pri izvršenju višenitnog programa je odnos izme đu broja nivoa prioriteta Java niti i broja nivoa prioriteta niti konkretnog operativnog sistema na kome će biti izvršen Java bajt-kod. Tako kod NT zasnovanih Windows-a poput trenutno uobi čajene verzije XP-a postoji 6 prioriteta: Realtime
High
Above Normal
Normal
Below Normal
Low
Problem nastaje kada se razli čiti Java nivoi prioriteta preslikaju u isti nivo kod tog operativnog sistema. Niti trebaju periodi čno da pozivaju yield() ili sleep() metodu kako bi drugim nitima pružile šansu da se izvrše. Na takav način se ukida mogu ćnost da neka nit ima svojevrsni monopol nad sistemom. Niti koje ne daju mogu ćnost drugim nitima da se izvrše nazivaju se sebičnim (selfish) nitima. Metoda yield() definisana u klasi Thread daje šansu drugim nitima, istog ili višeg prioriteta, da se izvrše u slu čaju kada se ne želi suspendovanje izvršenja niti odre đeno vreme metodom sleep(). Kada pozovete sleep() za neku nit, ona se neće nastaviti izvršavati bar za ono vreme koje je navedeno kao argument čak i ako nema niti koje čekaju. S druge strane, poziv metode yield() će dozvoliti da tekuća nit odmah nastavi ako nema niti koje čekaju:
yield(); //Vidi da li ima neka nit koja ceka, a ako nema nastavi
Grupe niti u Javi
CET Čitalište 72 – April 2009.
Višenitno programiranje (2) - Dragoljub Pilipović
Pojedini programi mogu da imaju ve ći broj konkurentnih niti. Tada je poželjno da se ove niti klasifikuju po nekoj funkcionalnosti radi lakšeg upravljanja. Java programski jezik omogu ćava formiranje grupe niti (thread group). Da bi se grupa kreirala mora se konstruktoru poslati kao argument jednozna čno ime u odnosu na imena drugih grupa niti:
ThreadGroup grupa_niti = new ThreadGroup(“Prva grupa niti”); Dodavanje neke niti grupi niti se izvodi na sledeći način:
Thread t = new Thread(grupa_niti, “Nova nit 1”); Ukoliko se želi proveriti koliko je niti u nekoj grupi niti u aktivnom stanju koristi se metoda activeCount():
if(grupa_niti.activeCount()) == 0) { System.out.println(“Sve niti u grupi su zaustavljene!”); } Sa interrupt() metodom nad objektom grupe niti mogu se prekinuti sve niti u grupi:
grupa_niti.interrupt(); //detaljnije za interrupt metodu pogledati 2.6 poglavlje Neka grupa niti može da sadrži podgrupu niti. Metode activeCount() i interrupt() se odnose na teku ću ili imenovanu grupu niti i sve njene podgrupe niti. Npr. ako se prekida izvršenje jedne grupe niti, prekidaju se tako đe i njene podre đene podgrupe niti.
Prekidanje i povezivanje niti u Javi Svaka nit se regularno završava kada se telo run() metode završi. Zato se koristi logi čka kontrola za nastavak izvršenja niti. Dokle god signal ima vrednost true, nit se izvršava:
public void run() { while(signal) { //ovde ide kod za izvrsavanje } } Kad se želi prekinuti izvršavanje onda se poziva metoda prekini(), koja menja vrednost promenljive signal na false:
public void prekini() { signal = false; }
CET Čitalište 72 – April 2009.
Višenitno programiranje (2) - Dragoljub Pilipović
Međutim u slučaju kada je nit u blokiranom stanju, prekid niti se mora obaviti interrupt() metodom. Ovom metodom neka nit signalizira drugoj niti da treba da zaustavi izvršavanje. Samo po sebi to ne će sa sigurnošću zaustaviti nit. Ono samo postavlja zastavicu (flag) u nit, a nit treba da u svojoj run() metodi proveri tu zastavicu da bi bilo nekog efekta:
druga_nit.interrupt(); //prekini drugu nit Na ovaj način će se prekinuti blokada niti generisanjem izuzetka InterruptException. Blokada može prestati ako je nastala na osnovu sleep() ili wait() metode. Metoda interrupt() prekida blokadu niti ali ne prekida izvršenje niti samo po sebi. Potrebno je obraditi izuzetak koji ona generiše na odgovaraju ći način:
public void run() { try { while(signal) { //ovde ide kod za izvrsavanje } } catch(InterruptedException e) { //prekinuta blokada nastala sleep() ili wait() metodom } //izlaz iz run() metode i prekid izvrsenja niti } Ipak kada se interrupt() metoda pozove za nit koja nije blokirana, ne će se desiti pomenuti izuzetak. Zbog toga se logička kontrola menja i dodaje se ispitivanje da li neka nit prekinuta ili nije sa metodom interrupted():
...
while( !interrupted() && signal ) { //ovde ide kod za izvrsavanje } ....
Statička interrupted() metoda ima sporedni efekat (side effect), jer pored testiranja i vra ćanja logičke vrednosti zastavice prekida, postavlja istu na false. Zato postoji druga nestati čka metoda iѕInterrupted() koja ne menja stanje zastavice prekida:
if(prva_nit.isInterrupted()) System.out.println(“Prva nit je nila prekinuta!”);
Treba naglasiti da kada se izbaci prekid InterruptedException, zastavica koja registruje prekid se briše, te će sledeći poziv interrupted() ili isInterrupted() vratiti false.
Ako u jednoj niti treba čekati dok druga nit ne završi sa radom koristi se metoda join() klase Thread, pa se te dve niti na taj način povezuju. Poziv ove metode bez argumenta će zaustaviti tekuću nit onoliko dugo koliko je potrebno specificiranoj niti da umre:
prva_nit.join(); //sacekajmo da prva nit zavrsi sa radom Metodu join() tako đe se može proslediti vrednost tipa long da bi odredili broj milisekundi za čekanje kraja specificirane niti:
prva_nit.join(1000); //cekaj najvise prvu nit 1 sekundu a onda nastavi sa radom
Višenitno programiranje (2) - Dragoljub Pilipović
CET Čitalište 72 – April 2009.
Komunikacija i sinhronizacija niti u Javi Komunikacija između niti se može obaviti na više načina. Jedan od njih je preko tzv. cevi (engl. pipes). Glavni razlog za korišćenje cevi je jednostavnost rada. Prva nit šalje bajtove preko cevi ne znaju ći koja će ih nit pročitati. Druga nit čita bajtove iz cevi ne znaju ći kako i ko ih je poslao u cev. Preko cevi se može povezati me đusobno više niti bez brige o njihovoj sinhronizaciji. Tipi čan problem koji se javlja je proizvođač-potrošač, za koji će biti ovde dat kompletan primer poznat u stru čnoj i akademskoj literaturi. Komunikacija između niti se obavlja tako što jedna nit, nazvana proizvo đač (producer), generiše niz bajtova, dok druga nit, nazvana potroša č, čita taj niz bajtova. Ako bajtovi nisu raspoloživi za čitanje, nit potrošač se blokira. Ako nit proizvođač generiše više bajtova nego što potrošač može da ih pro čita, nit proizvođač se blokira. Klase koje se koriste u komunikaciji izme đu niti su: PipedInputStream \ PipedOutputStream (kada se razmenjuju bajtovi) kao i PipedReader \ PipedWriter (kada se razmenjuju unicode znaci).
import java.util.*; import java.io.*; public class Cevi { public static void main(String args[]) throws IOException { PipedOutputStream cout1 = new PipedOutputStream(); PipedInputStream cin1 = new PipedInputStream(cout1); PipedOutputStream cout2 = new PipedOutputStream(); PipedInputStream cin2 = new PipedInputStream(); Proizvodjac pr = new Proizvodjac(cout1); Potrosac pot = new Potrosac(cin1); pr.start(); pot.start(); } } class Proizvodjac extends Thread { private DataOutputStream out; public Proizvodjac(OutputStream os) { out = new DataOutputStream(os); } public void run() { try { out.writeDouble(98.76); out.writeDouble(34.21); } catch(Exception ex) {} } } class Potrosac extends Thread { private DataInputStream in; public Potrosac(InputStream is) { in = new DataInputStream(is); } public void run() { try { double r1 = in.readDouble();
double r2 = in.readDouble(); System.out.println("Potrosac je primio ove brojeve: " + r1 + "
CET Čitalište 72 – April 2009.
Višenitno programiranje (2) - Dragoljub Pilipović
" + r2); } catch(Exception ex) {} } } Ukoliko se javi potreba da dve ili više niti dele zajedni čki resurs, pri čemu ne mogu istovremeno da koriste zajednički resurs, mora se obezbediti mehanizam koji će omogućiti isključivi (ekskluzivni) pristup jedne niti do zajedničkog resursa. Tek nakon završetka obrade zajedni čkog resursa od strane jedne niti, mogu će je da druga nit, takođe isključivo, pristupi do njega. Ovakav postupak se naziva sinhronizacija izme đu niti. Koncept monitora obezbeđuje mehanizam za isklju čivi pristup niti do zajedni čkog resursa. Kada neka nit K u đe u monitor, onda nijedna druga nit ne može u njega ući sve dok nit K ne iza đe iz njega. Postupak kojim monitor obezbeđuje navedeni mehanizam je upravo sinhronizacija. Kada nit pozove metodu objekta koji treba da se sinhronizuje, taj objekat postaje zaklju čan. Preciznije rečeno pri pozivu sinhronizovane metode: “Nit nalazi jedini klju č od objekta ispred vrata, ubacuje ključ u bravu od objekta, otključava ga, ulazi u objekat i bravu sa druge strane vrata zaključava. Brava ostaje zaključana sve dok nit ne iza đe iz objekta, odnosno dok nit ne izvadi klju č iz brave i stavi ga ispred vrata.” Niti koje čekaju na pristup sinhronizovanim metodama deljenog objekta, mogu pristupiti do nesinhronizovanih metoda deljenog objekta. U programskoj jeziku Java svaki objekat ima svoj monitor. Da bi se monitor pokrenuo potrebno je da se objektu pristupi preko jedne od njegovih sinhronizovanih metoda. Izvršavanje jedne od sinhronizovanih metoda nekog objekta onemogućava pristup do bilo koje druge sinhronizovane metode tog objekta. Ispred metode koja treba da bude sinhronizovana stavlja se klju čna reč syncronized:
class Zalihe { //klasa Zalihe je obicna/ne-nit tipa private int Kolicina; Zalihe() { Kolicina = 10; } syncronized int UzmiSaZaliha() { Kolicina--; //metoda UzmiSaZaliha smanjuje neki broj komada sa zaliha } } Moguće je i eksplicitno sinhronizovati objekat, odnosno blok koda:
class Nit extends Thread { public void run() { int PreostalaKolicina; syncronized (z) { PreostalaKolicina = z.Smanji(); } //samo ovo je sinhronizovano System.out.println(“Preostala kolicina: “ + PreostalaKolicina); } }
CET Čitalište 72 – April 2009.
Višenitno programiranje (2) - Dragoljub Pilipović
Komunikaciju između niti u Javi je mogu će ostvariti pomoću metoda wait(), notify() i notifyAll(), koje je mogu će pozivati jedino iz sinhronizovanih metoda/blokova koda. Kada nit u đe u sinhronizovanu metodu, može da dospe u blokirano stanje pomo ću wait() metode. Ta nit napušta monitor u kome se nalazi i prelazi u listu čekanja (engl. wait list):
wait(); //zaustavi tekucu nit dok se ne pozove notify() ili notifyAll() metoda Nit će uobičajeno pozvati wait() zato što nije postavljeno neko odre đeno svojstvo objekta na koji je nit sinhronizovana ili nije ispunjen neki uslov koji zavisi od akcije drugog metoda. Naj češća situacija je da resurs bude zauzet jer ga druga nit modifikuje. Postoji još i wait metoda koja suspenduje nit dok ne istekne prosle đeni broj milisekundi tipa long ili dok se ne pozove notify()/notifyAll() metoda:
syncronized public void run () { while(ListaZaObradu == null) { try { wait(5000); //sacekaj bar 5 sekundi ili notify obavestenje } catch(InterrptedException e) { //obrada izuzetka } //Lista za obradu vise nije prazna; obrada ide ovde } } U suspendovanom/blokiranom stanju nit ostaje sve dok neka druga nit ne pozove metodu notify(). Tada se nit opet vraća u monitor napuštajući listu čekanja. Ova metoda restartuje jednu nit koja je pozvala metodu wait() sinhronizovanu na isti objekat:
notify(); //obavestenje da je kljuc od brave vracen, neka ga uzme bilo ko Ako postoji više niti u listi za čekanje, samo jedna će u ći u monitor. Da bi sve one bile ravnopravno obaveštene, koristi se metoda notifyAll():
notifyAll(); //niti viseg prioriteta ce se prve vratiti u monitor
Autor: Dragoljub Pilipovi ć