500 likes | 711 Views
Vl ákna a konkurentné výpočty. dnes bude: konkurentné vs. p aral elné výpočty , vl ákna (threads) vs. proces y, komunikácia cez rúry (pipes), s ynchronizácia a k ritick á sekcia (semaf óry ), deadlock literat úra : Thinking in Java, 3rd Edition , 13.kapitola,
E N D
Vlákna a konkurentné výpočty dnes bude: • konkurentné vs. paralelné výpočty, • vlákna (threads) vs. procesy, • komunikácia cez rúry (pipes), • synchronizácia a kritická sekcia (semafóry), • deadlock literatúra: • Thinking in Java, 3rd Edition, 13.kapitola, • Concurrency Lesson, resp. Lekcia Súbežnosť, • Java Threads Tutorial, • Introduction to Java threads Prerekvizície (nepredpokladám, že ste zažili): • prednáška 29: vlákna v Pascale, http://pascal.input.sk/index.php/Start Cvičenia: • simuláciekonzolové či applety (ak treba, použiť existujúci kód), • napr. iné triedenie, iné guličky, plavecký bazén, lienky na priamke, ...
Procesy a vlákna • každý program v Jave obsahuje aspoň jedno vlákno (main), • okrem užívateľom definovaných vlákien, runtime spúšťa tiež “neviditeľné” vlákna, napríklad pri čistení pamäte, • pri aplikáciach, ktoré budú obsahovať GUI sa nezaobídeme bez vlákien, • každý bežný operačný systém podporuje vlákna aj procesy, • v prípade jedno/dvoj-procesorového systému OS musí zabezpečiť [preemptívne] prerozdelenie času medzi vlákna a procesy, • nepreemptívne plánovanie vymrelo s Win 3.0 a Win98/16bit, • na preemptívnom princípe ‘každý chvíľku ťahá pílku’ vzniká konkukrentný výpočet miesto skutočne paralelného výpočtu, • vlákna môžeme chápať ako jednoduchšie procesy, (ako jednoduchšie, to uvidíme)… • správu procesov riadi plánovač OS
Proces • je nezávislá entita na úrovni aplikácie, má svoj kód, dáta, systémový zásobník, program counter, ... • procesy môžu komunikovať cez rúry (pipes), súbory, či sokety (zástrčky , • runtime java vytvorí normálne jeden proces (ale bežne viac vlákien), • v jave vieme vytvoriť nový proces (trieda ProcessBuilder), ale urobíme si len malú ukážku: prvy: prvyprvyprvyprvyprvyprvy druhy: druhydruhydruhydruhyd import java.lang.*;import java.io.*;publicclass VytvorProces {publicstaticvoid main(String[] args) throws IOException {final ProcessBuilder pb =// vytvor nový proces/naklonuj sám sebanewProcessBuilder("java","VytvorProces","druhy");// cmdline+argsif (args[0].equals("prvy"))// ak si prvý process, tak spusti druhýpb.start(); // ak si druhý process, nepúšťaj ničFileWriter fw = new FileWriter(args[0]);// otvor súbor prvy/druhywhile (true)fw.write(args[0]);// bezhlavo píš do súboru}} Súbor: VytvorProces.java
Vlákna • odteraz sa venujeme už len vláknam, • vlákna sú „zjednodušené“ procesy, • každý proces má aspoň jedno (hlavné ) vlákno, • vlákna v rámci procesu zdieľajú prostriedky ako • pamäť (dáta, premenné, ...), • súbory a streamy, • zdieľanie prostriedkov niekedy pomáha (ako forma medzivláknovej komunikácie) a inokedy obmedzuje, • preto na riadenie zdieľania prostriedkov java poskytuje zabudované mechanizmy akými sú synchronizácia, či semafóry, • správu vlákien si riadi java runtime. Pekná inšpirácia a príklady http://www.cs.ubc.ca/~harrison/Java/sorting-demo.html http://maven.smith.edu/~thiebaut/java/sort/demo.html http://www.doc.ic.ac.uk/~jnm/book/book_applets/concurrency.html
Čo nás čaká o vláknach • vlákno je objekt nejakej podtriedy triedy Thread (package java.lang.Thread), • vlákno vieme vytvoriť (new Thread() , new SubTread()), • vlákno vieme spustiť (metóda Thread.start()), • vláknu vieme povedať, čo má robiť (vmetóde run() {…}), • vlákno vieme pozastaviť (Thread.yield()) a dať šancu iným vláknam, • vláknam vieme rozdať priority (Thread.setPriority()), akými bojujú o čas, • vlákno vieme uspať na dobu určitú (Thread.sleep()), • na vlákno vieme počkať, kým dobehne (Thread.join()), • na vlákno vieme prerušiť (Thread.interrupt()). Praktický pohľad na vlákna: • programy s vláknami sa ťažšie ľadia, • pri dvoch behoch rovnakého programu nemáme zaručené, že dôjde k rovnakej interakcii vlákien, ak však interagujú, • ľadenie chybnej synchronizácie vlákien je podobne náročné ako ak vám, “ktosi” prepisuje pamäť, • vo všeobecnosti, na konkurentné výpočty nie sme veľmi trénovaní...
Vlákna na príkladoch Krok za krokom: • nasledujúci príklad vytvorí a spustí 5 vlákien, • všetky vlákna sú podtriedou Thread, • konštruktor SimpleThread volá konštruktor triedy Thread s menom vlákna, • metóda getName() vráti meno vlákna, • každé vlákno si v cykle počíta v premennej countDown od 5 po 0 (metóda run()), • pri prechode cyklu vypíše svoj stav (metóda toString()), • keď countDown = 0 metóda run() dobehne, život vlákna končí, • aj keď si to priamo neuvedomujeme, vlákna zdieľajú výstupný stream System.out tým, že do neho „súčasne“ píšu.
Vytvorenie vlákna #1: 5 #1: 4 #1: 3 #1: 2 #1: 1 #3: 5 #3: 4 #3: 3 #3: 2 #3: 1 #5: 5 #5: 4 #5: 3 #5: 2 #5: 1 #2: 5 #2: 4 #2: 3 #2: 2 #2: 1 #4: 5 #4: 4 #4: 3 #4: 2 #4: 1 public class SimpleThread extends Thread { private int countDown = 5; private static int threadCount = 0; public SimpleThread() { super("" + (++threadCount)); // meno vlákna je threadCount start();// naštartovanie vlákna run() } public String toString() {// stav vlákna return "#" + getName() + ": " + countDown; } public void run() {// toto vlákno robí, ak je spustené while(true) { System.out.println(this);// odpočítava od countDown if(--countDown == 0) return; } } public static void main(String[] args) { for(int i = 0; i < 5; i++) new SimpleThread();// vytvorenie vlákna, ešte nebeží } } Súbor: SimpleThread.java
Zaťaženie vlákna #1: 5 #2: 5 #3: 5 #4: 5 #5: 5 #2: 4 #1: 4 #4: 4 #3: 4 #5: 4 #2: 3 #4: 3 #1: 3 #3: 3 #5: 3 #2: 2 #4: 2 #1: 2 #5: 2 #3: 2 #2: 1 #4: 1 #1: 1 #5: 1 #3: 1 • v predchádzajúcom príklade sme nemali pocit, že by vlákna bežali súbežne, • lebo čas pridelený plánovačom k ich behu im postačoval, aby vlákno prebehlo a skončilo metódu run(), • preto pridajme im viac roboty, príklad je umelý ale ilustratívny public void run() { while(true) { System.out.println(this); for(int j=0; j<50000000; j++) {// kým toto zráta double gg = 0-Math.PI+j+j-j+Math.PI;// zapotí sa ... } if(--countDown == 0) return; } } • toto je jedna možnosť, ako pozdržať/spomaliť výpočet vlákna, ktorá však vyčerpáva procesor (pozrite si CPU load), • ak chceme, aby sa počas náročného výpočtu vlákna dostali k slovu aj iné vlákna, použijeme metódu yield() – „daj šancu iným“, resp. nastavíme rôzne priority vlákien, viď nasledujúce príklady Súbor: SimpleThread2.java
Pozastavenie/uvoľnenie vlákna • metóda yield() zistí, či nie sú iné vlákna v stave pripravenom na beh (Runnable), • ak sú, dá im prednosť. public void run() { while(true) { System.out.println(this); for(int j=0; j<50000000; j++) {// kým toto zráta // zapotí sa ... double gg = 0-Math.PI+j+j-j+Math.PI; } yield();// daj šancu iným if(--countDown == 0) return; } } • iná možnosť spočíva v nastavení priorít vlákien, • pripomeňme si, že vlákna nie sú procesy na úrovni OS, • plánovač vlákien pozná 10 úrovní priorít z intervalu MAX_PRIORITY(10), MIN_PRIORITY(1),ktoré nastavíme pomocousetPriority(int newPriority) #1: 5 #2: 5 #4: 5 #3: 5 #5: 5 #2: 4 #4: 4 #3: 4 #5: 4 #1: 4 #2: 3 #1: 3 #4: 3 #3: 3 #5: 3 #2: 2 #4: 2 #3: 2 #1: 2 #5: 2 #2: 1 #4: 1 #3: 1 #5: 1 #1: 1 Súbor: YieldingThread.java
Priority vlákna Thread[Thread-1,1,main] Thread[Thread-0,10,main] Thread[Thread-2,1,main] Thread[Thread-0,10,main] Thread[Thread-0,10,main] Thread[Thread-1,1,main] Thread[Thread-0,10,main] Thread[Thread-1,1,main] Thread[Thread-0,10,main] Thread[Thread-1,1,main] Thread[Thread-1,1,main] Thread[Thread-4,1,main] Thread[Thread-3,1,main] Thread[Thread-4,1,main] Thread[Thread-3,1,main] Thread[Thread-4,1,main] Thread[Thread-3,1,main] Thread[Thread-4,1,main] Thread[Thread-3,1,main] Thread[Thread-4,1,main] Thread[Thread-3,1,main] Thread[Thread-5,1,main] Thread[Thread-2,1,main] Thread[Thread-5,1,main] Thread[Thread-2,1,main] Thread[Thread-5,1,main] Thread[Thread-5,1,main] Thread[Thread-2,1,main] Thread[Thread-5,1,main] Thread[Thread-2,1,main] public class PriorityThread extends Thread { private int countDown = 5; private volatile double d = 0; // d je bez optimalizácie public PriorityThread (int priority) { setPriority(priority);// nastavenie priority start();// spustenie behu vlákna } public void run() { while(true) { for(int i = 1; i < 100000; i++) d = d + (Math.PI + Math.E) / (double)i; System.out.println(this); if(--countDown == 0) return; } } public static void main(String[] args) { new PriorityThread (Thread.MAX_PRIORITY); //#0=10 for(int i = 0; i < 5; i++) new PriorityThread (Thread.MIN_PRIORITY); //#i=1 } } Súbor: PriorityThread.java
Interface Runnable • iná možnosť, ako vytvoriť vlákno, je trieda, ktorá implementuje interface Runnable, • vtedy vytvoríme vlákno pomocou konštruktora new Thread(this), • takáto trieda potom musí obsahovať metódu void run(), ktorá sa spustí po start(), POZOR, NIE run() !!!! Okrem toho je nasledujúci príklad prvým príkladom appletu: • podtrieda triedy Applet poskytuje grafické rozhranie, • bližšie na budúcej prednáške, • ale škoda robiť simulácie procesov bez možnosti si ich graficky zobraziť... Čo k appletom potrebujeme na štart: • nasledujúci applet má: • inicializačnú metódu void init(), • po nej sa volá metóda void start(), • zobrazenie/prekreslenie appletu je metóda void paint(Graphics g), • tutorial pre grafiku napr. tu • spúšťa sa v Eclipse ako applet Alt+Shift+X-A, resp. pomocou appletviewer-a
import java.applet.*; import java.awt.*; public class Rucicka extends Applet implements Runnable { int width, height; int i = 0; Thread t = null; public void init() { width = getSize().width; height = getSize().height; setBackground( Color.BLUE ); } public void start() { t =new Thread( this ); t.start(); } } Ručička public void run() { try { while (true) { i++; i %= 60; repaint(); t.sleep( 1000 ); } } catch (InterruptedException e) { /*…*/ } } public void paint( Graphics g ) { g.setColor( Color.ORANGE ); g.drawLine( width/2, height/2, (int)(width/2+width/3*Math.cos(i/30.0*Math.PI)), (int)(height/2+height/3*Math.sin(i/30.0*Math.PI))); } Súbor: Rucicka.java
Guličky v krabici • nasledujúci príklad ilustruje simuláciu dvoch jednoduchých „procesov“, • v krabici lietajú dve rôznofarebné guličky, • každá z guličiek je simulovaná jedným vláknom, • toto vlákno si udržiava lokálny stav simulovaného objektu, t.j. • polohu, súradnice [x,y], • smer, vektor rýchlosti [dx, dy], • farbu, event. rýchlosť, ... • metóda run() počíta nasledujúci stav (polohu, smer) objektu (guličky), • treba k tomu trochu “fyziky” (lebo uhol dopadu sa rovná uhlu odrazu), • keďže strany krabice sú rovnobežne so súradnicami, stačí si uvedomiť, že • ak gulička nenarazí, jej nová poloha je [x+dx, y+dy], • ak gulička narazí, zmení sa jej smerový vektor na [Ŧdx, Ŧdy], • po každom kroku simulácie si vlákno vynúti prekreslenie (časti) appletu, t.j. vlákno má odkaz na hlavný program (applet), • hlavný program (applet) len: • vytvorí obe vlákna a naštartuje ich, • vykreslí polohu/stav guličiek (to musí vidieť ich súradnice, ktoré sú vo vláknach)
Guličky - thread public class Gulicka extends Thread { public int x = ..., y = …, dx = …, dy = … // stav guličky Ball ap; // odkaz na hlavný applet, uložíme si v konštruktore public void run() { while (true) { x += dx; y += dy;// urob krok if (x >= sizeX) { // odrážanie od stien x = sizeX - (x-sizeX); dx = -dx; } else if (y >= sizeY) { y = sizeY - (y-sizeY); dy = -dy; } else if (x < 0) { x = -x; dx = -dx; } else if (y < 0) { y = -y; dy = -dy; }// simulácia má svoje rezervy v rohoch ap.repaint();// prekresli applet s gulickami try { sleep(10); // pockaj 0.01 s – simuluj krok } catch(Exception e) {…} } } } Súbor: Gulicka.java
Guličky - applet public class Ball extends Applet { Gulicka th1, th2;// dve vlákna pre gulicky . . . . . public void start() { th1 = new Gulicka (this);// vytvor vlákno pre modrú th1.start();// štartuj modrú guličku th2 = new Gulicka (this); // vytvor vlákno červenej th2.start();// štartuj červenú guličku } public void paint(Graphics g) { // vypíš ich ubehnuté kroky do stavového riadku appletu g.setColor(Color.BLUE); g.drawRect(th1.x,th1.y,2,2);// pozri do vlákna //na súradnice modrej g.setColor(Color.RED);// pozri do vlákna g.drawRect(th2.x,th2.y,2,2); // na súradnice červenej } . . . . } Súbor: Ball.java
Hra Bomba-Štít public class Bojovnici extends Applet { final int N = 100; Bojovnik[] bojovnik = new Bojovnik[N]; // pole všetkých bojovníkov Color[] cols = {Color.RED, Color.BLUE, Color.GREEN, Color.CYAN, Color.YELLOW}; public void init() {// inicializácia appletu resize(300,300); Random rnd = new Random();// generátor náhody for(int i=0; i<N; i++) // vytvorenie bojovníkov bojovnik[i] = new Bojovnik(this, // čobojovník to vlákno rnd.nextInt(300),rnd.nextInt(300),// náhodná pozícia na začiatok cols[rnd.nextInt(cols.length)]);// farba bojovníka pre efekt for(int i=0; i<N; i++) { // priradenie zabijáka a štítu bojovnik[i].zabijak(bojovnik[(i+1)%N]); // nasledujúci je bomba-killer bojovnik[i].stit(bojovnik[(i>0)?i-1:N-1]); // predchádzajúci je štít-defender } for(int i=0; i<N; i++) // spustenie všetkých vlákien bojovnik[i].start(); } Hraje N ľudí, každý má určený jedného hráča ako štít, jedného ako bombu, pričom sa snaží postaviť tak, aby ho štít chránil pred bombou (t.j. boli v priamke) Súbor: Bojovnici.java
Vykreslenie bojovníkov import java.applet.*; import java.awt.*; import java.util.*; public class Bojovnici extends Applet { . . . . . . public void paint(Graphics g) { for(int i=0; i<N; i++) { g.setColor(bojovnik[i].col); g.drawOval((int)Math.round(bojovnik[i].x), (int)Math.round(bojovnik[i].y), 3,3); } } } Súbor: Bojovnici.java
Správanie bojovníka public class Bojovnik extends Thread { // lokálny stav bojovníka public double x,y; //public int x,y;// --- súradnice public Color col;// --- farba Bojovnik killer, defender;// kto je jeho bomba a štít Bojovnici applet;// pointer na nadradený applet public Bojovnik(Bojovnici applet, int x, int y, Color col) {// konštruktor this.x = x; this.y = y; this.col = col;this.applet = applet;} public void zabijak(Bojovnik killer) {this.killer = killer;} // set killer public void stit(Bojovnik defender) {this.defender=defender;}// set defender public void run() { while (true) {// súradnice bodu, kam sa treba teoreticky postaviť, aby // defender bol v strede medzi mnou a killerom double xx = 2*defender.x - killer.x; // rovnica priamky, nič viac... double yy = 2*defender.y - killer.y; double laziness = 0.1;// parameter lenivosti (0.0-1.0) x = (xx-x)*laziness+x;// ako rýchlo smerujem do xx,yy y = (yy-y)*laziness+y;// nové súradnice bojovníka applet.repaint(); try { sleep(100); } catch(Exception e) {..} // 0.1 sek. Konštanta pre efekt } } Súbor: Bojovnik.java
Trojuholníky • nasledujúca hra trojuholníky je analógia hry Bomba-štít, • určení hráči majú tvoriť vrcholy rovnostranného trojuholníka, • simulácia je však urobená tak, že • hlavné vlákno (Trojuholniky) vykresluje stav (polohu a farby trojuholníkov) for(int i=0; i<th.N; i++) { g.setColor(cols[i%cols.length]); g.drawRect(th.x[i],th.y[i],3,3); g.drawLine(th.x[i-1],th.y[i-1],th.x[i+1],th.y[i+1]); g.drawLine(th.x[i],th.y[i],th.x[i+1],th.y[i+1]); g.drawLine(th.x[i-1],th.y[i-1],th.x[i],th.y[i]); } • jedno pomocné vlákno (vrcholy) simuluje pohyb všetkých bodov • ako zo súradníc A, B vyrátame súradnice vrchola C rovnostranného trojuholníka ? ... Trochu matiky... • ako sa tam dostaneme z D? C D A B
Hra Trojuholníky public class Vrcholy extends Thread { Trojuholniky applet; int N = 32; int[] x; // x-ové súradnice bodov int[] y;// y-ové súradnice bodov public void run() { int i = 0; while (true) { int i1 = i-1;if (i1 < 0) i1 = N-1; int i2 = i+1;if (i2 == N) i2 = 0; // vypočítaj nové súradnice bodu x[i],y[i] s vrcholmi x[i1],y[i1] a x[i2],y[i2] double laziness = 0.01; double new_X = x[i] + (double)(X-x[i])*laziness; double new_Y = y[i] + (double)(Y-y[i])*laziness; i++;if (i == N) i=0;// ďalší bod applet.repaint(); try { sleep(4);} catch(Exception e) {} } Súbor: Trojuholnik.java, Vrcholy.java
Preteky v triedení • motivačný príklad o triedeni na začiatku prednášky je trochu podvod, • bežia tam síce súbežné vlákna, ale nebežia v jave, ale v browseri, • ďalší príklad je pretekom 4 triediacích algoritmov v jave, • hlavný applet je rozdelený na 4 panely (SortPanel extends java.awt.Panel), • každý SortPanel • náhodne vygeneruje pole na triedenie, • vytvorí vlákno triedy SortThread a spustí, • každý SortPanel si vyvorí miesto na kreslenie paličiek (SortCanvas extends java.awt.Canvas), • SortThread triedi vygenerované pole daným algoritmom (parameter ”buble”), • SortCanvas poskytuje SortThread metódu swap(i,j) – prvky i, j sa vymenili • SortCanvas zabezpečuje vykreslovanie paličiek do SortPanelu, • vymenené paličky (hi, lo) znázorní čierne, • Random sort je jediný (len mne) známy algo triedenia horši ako bublesort i = random(); j = random(); if (i<j && a[i] > a[j]) { int pom = a[i]; a[i] = a[j]; a[j] = pom; }
Sorty public class SortApplet extends Applet { SortPanel buble, quick, merge, random; public void init() { .... buble = new SortPanel("Buble",Color.MAGENTA); add(buble); quick = new SortPanel("Quick",Color.BLUE); add(quick); merge = new SortPanel("Merge",Color.RED); add(merge); random = new SortPanel("Random",Color.GREEN); add(random); } public void start() { buble.start(); quick.start(); merge.start(); random.start(); } } Súbor: SortApplet.java
SortPanel, Canvas public class SortPanel extends Panel { SortThread thread ; SortCanvas can; int[] a; . . . . public void start() { a = new int[100]; for(int i=0; i < a.length; i++) a[i] = (int)(200*Math.random()); thread = new SortThread(can, algo, a); thread.start(); } } public class SortCanvas extends Canvas { int lo, hi;// ktoré dve sa vymenili public void swap(int i, int j) { lo = i; hi = j; } public void update(Graphics g) { g.clearRect(0,0,200,200); Color cl = g.getColor(); for(int i=0; i < sp.a.length; i++) { g.setColor((i == lo || i == hi)? Color.BLACK:cl); g.drawLine(2*i,sp.a[i],2*i,0); //g.drawLine(2*i,sp.a[i],2*i,sp.a[i]); } } } Súbor: SortPanel.java Súbor: SortCanvas.java
RandomSrt public class SortThread extends Thread { SortCanvas can; public void run() { if (algo.equals("Buble")) bubleSort(a); . . . . else randomSort(a); } void swap(int i, int j) { can.swap(i,j); can.repaint(); try{ sleep(10); } catch(Exception e) {} } void randomSort(int a[]) { while(true) { int i = (int)((a.length-1)*Math.random()); int j = (int)((a.length-1)*Math.random()); swap(i,j); if (i<j && a[i] > a[j]) { int pom = a[i]; a[i] = a[j]; a[j] = pom; } } } Súbor: SortThread.java
Korytnačky • ďalšie dva príklady sú simulácii viacerých korytnačiek, • korytnačie príkazy poznáte z prednášky Delphi, • 1.verzia (Turtles), podobná triedeniam, rozdelíme plochu appletu štyrom korytnačkám, • vlákna nezávisle píšu do appletu, ale nezdieľajú žiaden spoločný Canvas, • 2.riešenie (Korytnačky) maľuje do toho istého priestoru – tu musíme byť opatrnejší... Nabudúce: • ak viacero vlákien chce zdieľať jeden zdroj/prostriedok (pamäť, stream, ...) musíme byť opatrnejší, • java ponúka prostriedky na synchronizáciu súbežných vlákien, • semafóry na riešenie kritických oblastí ... situácií...
Korytnačky Korytnačí program: for(int i=0; i < steps; i++) { Dopredu(init); init+=delta; Vpravo(uhol); } public void Vlavo(float Uhol) { dir -= Uhol; } public void Vpravo(float Uhol) { dir += Uhol; } public void Dopredu(float Dlzka) { double X1 = X + (float)(Dlzka*Math.cos(dir*2*Math.PI/360)); double Y1 = Y + (float)(Dlzka*Math.sin(dir*2*Math.PI/360)); sp.can.getGraphics().drawLine((int)X,(int)Y,(int)X1,(int)Y1); X = X1; Y = Y1; } Súbor: Turtles.java, TurtlePanel.java, TurtleCanvas.java, TurtleThread.java
Korytnačky ešte raz... public class Korytnacky extends Applet { Korytnacka k1, k2, k3, k4; BufferedImage bufImage = new BufferedImage(sizeX, sizeY, BufferedImage.TYPE_INT_ARGB); public void start() { k1 = new Korytnacka(this, 100,100, 0, 1, 91); k1.start(); // . . . . k4 = new Korytnacka(this, 100, 300, 0, 1, 59); k4.start(); } public void paint(Graphics g) { g.drawImage(bufImage,0,0,Color.BLUE,this); // off-screen image } } Súbor: Korytnacky.java
Vlákna a konkurentné výpočty(pokračovanie) dnes bude: • komunikácia cez rúry (pipes), • synchronizácia a kritická sekcia (semafóry), • deadlock literatúra: • Thinking in Java, 3rd Edition, 13.kapitola, • Concurrency Lesson, resp. Lekcia Súbežnosť, • Java Threads Tutorial, • Introduction to Java threads Prerekvizície (predpokladám, že ste zažili): • prednáška 29: vlákna v Delphi, http://delphi.input.sk/index.php/Start Cvičenia: • simuláciekonzolové či applety (ak treba, použiť existujúci kód), • napr. iné triedenie, iné guličky, plavecký bazén, lienky na priamke, ...
Pozastavenie/uspanie vlákna • zaťaženie vlákna (nezmyselným výpočtom) vyčerpáva procesor, potrebujeme jemnejšiu techniku, • nasledujúci príklad ukáže, ako uspíme vlákno bez toho aby sme zaťažovali procesor nepotrebným výpočtom, • vlákno uspíme na čas v milisekundáchmetódou Thread.sleep(long millis) throws InterruptedException, • spánok vlákna môže byť prerušený metódou Thread.interrupt(), preto pre sleep musíme ošetriť výnimku InterruptedException, • ak chceme počkať, kým výpočeť vlákna prirodzene dobehne (umrie), použijeme metódu Thread.join() • ak chceme testovať, či život vlákna bol prerušený, použijeme metódu boolean isInterrupted(), resp. Thread.interrupted().
Uspatie vlákna #1: 5 #1: 4 #1: 3 #1: 2 #1: 1 -- #2: 5 #2: 4 #2: 3 #2: 2 #2: 1 -- #3: 5 #3: 4 #3: 3 #3: 2 #3: 1 -- #4: 5 #4: 4 #4: 3 #4: 2 #4: 1 -- #5: 5 #5: 4 #5: 3 #5: 2 #5: 1 -- public class SleepingThread extends Thread { private int countDown = 5; private static int threadCount = 0; public SleepingThread() { … .start(); } public void run() { while(true) { System.out.println(this); if(--countDown == 0) return; try { sleep(100);// uspi na 0.1 sek. } catch (InterruptedException e) { // výnimku musíme ochytiť throw new RuntimeException(e); // spánok bol prerušený } } } public static voidmain(String[] args) throws InterruptedException { for(int i = 0; i < 5; i++) { new SleepingThread().join();// počkaj kým dobehne System.out.println("--"); } } } Súbor: SleepingThread.java
Čakanie navlákno • nasledujúci príklad vytvorí 4 vlákna, • dva (Prvy, Druhy) triedy Sleeper, ktorý zaspia na 1.5 sek. • ďalšie dva (Treti, Stvrty) triedy Joiner, ktoré sa metódou join() pripoja na sleeperov a čakajú, kým dobehnú, • aby vedelo vlákno triedy Joiner, na koho má čakať, konštruktor triedy Joiner dostane odkaz na vlákno (sleepera), na ktorého má čakať, • medzičasom, výpočet vlákna Prvy násilne zastavíme v hlavnom vlákne metódou interrupt(). // hlavný thread: Sleeper prvy = new Sleeper("Prvy", 1500); Sleeper druhy = new Sleeper("Druhy", 1500), Joiner treti = new Joiner("Treti", druhy), Joiner stvrty = new Joiner("Stvrty", prvy); prvy.interrupt();
Čakanie navlákno - Sleeper class Joiner extends Thread { private Sleeper sleeper; public Joiner(String name, Sleeper sleeper) { super(name); this.sleeper = sleeper; start(); } public void run() { try { sleeper.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(getName() + "dobehol"); } } class Sleeper extends Thread { private int duration; public Sleeper( String name, int sleepTime) { super(name); duration = sleepTime; start(); } public void run() { try { sleep(duration); } catch (InterruptedException e) { System.out.println(getName() + " preruseny"); return; } System.out.println(getName() + " vyspaty"); } } Súbor: Sleeper.java
Čakanie navlákno - Joiner class Joiner extends Thread { private Sleeper sleeper; public Joiner(String name, Sleeper sleeper) { super(name); this.sleeper = sleeper; start(); } public void run() { try { sleeper.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(getName() + " dobehol"); } } class Sleeper extends Thread { private int duration; public Sleeper(String name, int sleepTime) { super(name); duration = sleepTime; start(); } public void run() { try { sleep(duration); } catch (InterruptedException e) { System.out.println(getName() + " preruseny"); return; } System.out.println(getName() + " vyspaty"); } } Prvy preruseny Stvrty dobehol Druhy vyspaty Treti dobehol Súbor: Joiner.java
Komunikácia medzi vláknami • doteraz sme mali príklady vlákien, ktoré medzi sebou (počas ich behu…) nekomunikovali (ak teda nerátame za komunikáciu, že sa zabíjali), • ak chceme, aby si vlákna vymieňali dáta, vytvoríme medzi nimi rúru (pipe), • rúra pozostáva z jednosmerne orientovaného streamu, ktorý sa na strane zapisovača (producenta, Sender) tvári ako PipedWriter, a na stranečítača (konzumenta, Reader) ako PipedReader, • aby čítač čítal z rúry, ktorú zapisovač pre neho vytvoril, musíme mu poslať odkaz na vytvorenú rúru PipedWriter, inak máme dve rúry... • do rúry možeme písať bajty, znaky, reťazce, objekty, v závislosti, ako si rúru zabalíme (viď techniky z I/O prednášky), • vytvoríme objekt Sender (producent), ktorý do rúry zapíše znaky A, B, ..., z • objekt Reader (konzument), ktorý číta znaky z rúry a vypíše A, B, ..., z public class SenderReceiver {// hlavný program public static void main(String[] args) throws Exception { Sender sender = new Sender(); Receiver receiver = new Receiver(sender); sender.start(); receiver.start(); } }
Výstupná rúra class Sender extends Thread { private Random rand = new Random(); private PipedWriter out = new PipedWriter(); // vytvor rúru na zápis, rúra je ukrytá, private public PipedWriter getPipedWriter() { return out; // daj rúru, bude ju potrebovať Reader na nadviazanie spojenia } public void run() { while(true) { for(char c = 'A'; c <= 'z'; c++) { try { out.write(c);// vypíš znaky abecedy do rúry sleep(rand.nextInt(500));// a za každým počkaj max.½ sek. } catch(Exception e) { throw new RuntimeException(e); } } } Súbor: Sender.java
Vstupná rúra class Receiver extends Thread { private PipedReader in; public Receiver(Sender sender) throws IOException { in = new PipedReader(sender.getPipedWriter());// vytvor vstupnú }// rúru napojenú na výstupnú rúru Sendera public void run() { try { while(true)// čítaj zo vstupnej rúry a píš na konzolu System.out.println("Read: " + (char)in.read()); } catch(IOException e) { throw new RuntimeException(e); } } } Read: A Read: B Read: C Read: D Read: E Read: F Read: G Read: H Read: I Read: J Read: K Read: L Read: M Read: N Read: O Read: P Read: Q Read: R Súbor: Receiver.java
Synchronizácia • v prípade, ak dve vlákna zdieľajú nejaký zdroj, môže dôsť k nepredvídateľnej interakcii vlákien (napr. jeden číta, druhý píše), • spôsob, akým sa riadi prístup k zdieľaným zdrojom (synchronizácia) sa volá: • kritická sekcia, • semafór, mutex, PV operácie, • java monitor. • skúsime si sami naprogramovať semafór, aby sme pochopili, prečo táto vlastnosť musí byť súčasťou jazyka, a nie naprogramovaná v jazyku, • semafór reprezentuje celočíselná premenná semaphore inicializovaná na 0, • ak je zdieľaný zdroj voľný, semaphore == 0, • záujem použiť zdroj vyjadrím pomocou aquire(), • ak prestanem používať zdroj, uvoľním ho pomocou release(). • Najivná implementácia vedie k tomu, že dve vlákna sa v istom čase dozvedia, že zdroj je voľný, oba si ho zarezervujú, a dochádza ku kolízii
Semafór public class SemaphoreTester extends Thread { public void run() { while(true) if(semaphore.available()) { yield();// skôr to spadne semaphore.acquire(); yield(); semaphore.release(); yield(); } } public static void main(String[] args) throws Exception { Semaphore sem = new Semaphore(); new SemaphoreTester(sem); new SemaphoreTester(sem); } } public class Semaphore { // neoptimalizuj ! private volatile int semaphore = 0; // môžem vojsť ? public boolean available() { return semaphore == 0; } // idem dnu ! public void acquire() { ++semaphore; } // odchádzam... public void release() { --semaphore; } }
Synchronizovaná metóda Riešenie: Java ponúka konštrukciu synchronized: • synchronizovaná metóda– nie je možné súčasne volať dve synchronizované metódy toho istého objektu (kým sa vykonáva jedna synchronizovaná, ostatné sú pozastavené do jej skončenia). public class SynchronizedSemaphore extends Semaphore { private volatile int semaphore = 0; public synchronized boolean available() { return semaphore == 0; } public synchronizedvoid acquire() { ++semaphore; } public synchronizedvoid release() { --semaphore; } ... a teraz to už pojde ? public void run() { while(true) if(semaphore.available()) { semaphore.acquire(); semaphore.release(); } }
Synchronizovaná (kritická) sekcia Atomická operácia: • sú operácie, ktoré sú nedeliteľné pre plánovač vlákien, napr. nie je možné, aby jedno vlákno zapísalo len spodné 2 bajty do premennej int, • čítanie a zápis do premenných primitívnych typov a premenných deklarovaných ako volatile je atomická operácia. ale • operácie nad zložitejšími štruktúrami nemusia byť synchronizované (napr. ArrayList, HashMap, LinkedList, … (v dokumentácii nájdete Note that this implementation is not synchronized). Riešenie: synchronizovaná sekcia – správa sa podobne ako synchronizovaná metóda, ale musí špecifikovať objekt, na ktorý sa synchronizácia vzťahuje. • while(true) • synchronized(this) { • if(semaphore.available()) { • semaphore.acquire(); • semaphore.release(); • }
Nesynchronizovaný prístup Iný, praktickejší príklad dátovej štruktúry, ku ktorej nesynchronizovane pristupujú (modifikujú ju) dve vlákna: publicclass ArrayListNotSynchronized extends Thread { ArrayList<Integer> al = new ArrayList<Integer>(); // štruktúra int counter = 0; // počítadlo //not synchronized publicvoid add() { System.out.println("add "+counter); al.add(counter); counter++; // pridaj prvok do štruktúry } //not synchronized publicvoid delete() { if (al.indexOf(counter-1) != -1) { // nachádza sa v štruktúre System.out.println("delete "+(counter-1)); al.remove(counter-1); counter--; // vyhoď zoštruktúry } } } Súbor: ArrayListNotSynchronized .java
Pokračovanie – dve vlákna Vlákno t1 pridáva prvky, vlákno t2 maže zo štruktúry publicclass ArrayListThread extends Thread { boolean kind; static ArrayListNotSynchronized al = new ArrayListNotSynchronized(); public ArrayListThread(boolean kind) { this.kind = kind; } publicvoid run() { while (true) { if (kind) al.add(); else al.delete(); } } publicstaticvoid main(String[] args) { ArrayListThread t1 = new ArrayListThread(true); t1.start(); ArrayListThread t2 = new ArrayListThread(false); t2.start(); } } • … a dostaneme (keď zakomentujeme System.out.println): • Exception in thread "Thread-2" java.lang.IndexOutOfBoundsException: Index: 17435, Size: 17432 • at java.util.ArrayList.RangeCheck(Unknown Source) • at java.util.ArrayList.remove(Unknown Source) • at ArrayListNotSynchronized.delete(ArrayListNotSynchronized.java:15) • at ArrayListThread.run(ArrayListThread.java:12) Súbor: ArrayListThread.java
Synchronizovaná metóda vs. štruktúra publicclass ArrayListNotSynchronized extends Thread { ArrayList<Integer> al = new ArrayList<Integer>(); int counter = 0; synchronizedpublicvoid add() {al.add(counter); counter++;} synchronizedpublicvoid delete() { if (al.indexOf(counter-1) != -1) { al.remove(counter-1); counter--;} } publicclass ArrayListSynchronized extends Thread { List al = Collections.synchronizedList(new ArrayList()); int counter = 0; publicvoid add() { al.add(counter); counter++; } publicvoid delete() { if (al.indexOf(counter-1) != -1) { al.remove(counter-1); counter--; } } } Súbory: ArrayListNotSynchronized .java, ArrayListSynchronized .java
Monitor a čakacia listina Každý objekt má monitor, ktorý obsahuje jediné vlákno v danom čase. Keď sa vstupuje do synchronizovanej sekcie/metódy viazanej na tento objekt, vlákno sa poznačí v monitore. Ak sa opäť pokúša vlákno dostať do synchronizovanej sekcie, monitor už obsahuje iné vlákno, preto je vstup do sekcie pozastavený, kým toto neopustí sekciu (a monitor sa uvoľní). Každý objekt má čakaciu listinu – tá obsahuje vlákna uspané prostredníctvom volania objekt.wait(), ktoréčakajú, kým iné vlákno prebudí tento objekt prostredníctvom objekt.notify(). public class Semaphore{ private int value; public Semaphore(int val) { value = val; } public synchronized void release() { ++value; notify(); // this.notify(); } } public synchronized void acquire() { while (value == 0) try { wait();// this.wait(); } catch (InterruptedException ie) { } value--; } java.util.concurrent.Semaphor
Ohraničený buffer Príklad: producer-consumer: // zapíš objekt do buffra public synchronized void put(Object o) throws InterruptedException { while (count==size) wait();// kým je buffer plný, čakaj... buf[in] = o; ++count; in=(in+1) % size; notify();// keď si zapísal, informuj čakajúceho } // vyber objekt do buffra public synchronized Object get() throws InterruptedException { while (count==0) wait();// kým je buffer prázdny, čakaj... Object o =buf[out]; buf[out]=null; --count; out=(out+1) % size; notify();// keď si vybral prvok, informuj ... return (o); } Zdroj: http://www.doc.ic.ac.uk/~jnm/book/book_applets/concurrency.html
Stavy vlákna • new – nenaštartovaný ešte, • runnable – može bežať, keď mu bude pridelený CPU, • dead – keď skončí metóda run(), resp. po stop(), • blocked – niečo mu bráni, aby bežal: • sleep(miliseconds) – počká daný čas, ak nie je interrupted... • wait(), resp. wait(milisec) čaká na správu notify() resp. notifyAll() , • čaká na I/O, • pokúša sa zavolať synchronized metódu. sleep vs. wait keď proces volá wait(), výpočet je pozastavený, ale iné synchronizované metódy môžu byt volané
Večerajúci filozofovia class Fork { private boolean taken=false; private PhilCanvas display; private int identity; Fork(PhilCanvas disp, int id) { display = disp; identity = id;} synchronized void put() { taken=false; display.setFork(identity,taken); notify(); } synchronized void get() throws java.lang.InterruptedException { while (taken) wait(); taken=true; display.setFork(identity,taken); } } Súbor: Fork.java Zdroj: http://www.cse.psu.edu/~catuscia/teaching/cg428/Concurrency_applets/concurrency/diners/
3 2 2 1 3 4 1 4 0 0 Večerajúci filozofovia class Philosopher extends Thread { private PhilCanvas view; . . . . public void run() { try { while (true) { // thinking view.setPhil(identity,view.THINKING); sleep(controller.sleepTime()); // hungry view.setPhil(identity,view.HUNGRY); right.get(); // gotright chopstick view.setPhil(identity,view.GOTRIGHT); sleep(500); left.get(); // eating view.setPhil(identity,view.EATING); sleep(controller.eatTime()); right.put(); left.put(); } } catch (java.lang.InterruptedException e){} } } Súbor: Philosopher.java Zdroj: http://www.cse.psu.edu/~catuscia/teaching/cg428/Concurrency_applets/concurrency/diners/
http://www.doc.ic.ac.uk/~jnm/book/book_applets/Diners.html Večerajúci filozofovia Phil 0 thinking Phil 0 has Chopstick 0 Waiting for Chopstick 1 Phil 0 eating Phil 0 thinking Phil 0 has Chopstick 0 Waiting for Chopstick 1 Phil 0 eating Phil 0 thinking Phil 0 has Chopstick 0 Waiting for Chopstick 1 Phil 0 eating Phil 0 thinking Phil 0 has Chopstick 0 Waiting for Chopstick 1 Phil 0 eating Phil 0 thinking Phil 0 has Chopstick 0 Waiting for Chopstick 1 Phil 0 eating Phil 0 thinking Phil 0 has Chopstick 0 Waiting for Chopstick 1 Phil 1 thinking Phil 2 thinking Phil 3 thinking Phil 4 thinking Phil 1 has Chopstick 1 Waiting for Chopstick 2 Phil 2 has Chopstick 2 Waiting for Chopstick 3 Phil 3 has Chopstick 3 Waiting for Chopstick 4 Phil 4 has Chopstick 4 Waiting for Chopstick 0 for (int i =0; i<N; ++i) fork[i] = new Fork(display,i); for (int i =0; i<N; ++i){ phil[i] = new Philosopher (this,i,fork[(i-1+N)%N],fork[i]); phil[i].start(); }
Poučenývečerajúci filozof class Philosopher extends Thread { private PhilCanvas view; . . . . public void run() { try { while (true) { // thinking view.setPhil(identity,view.THINKING); sleep(controller.sleepTime()); // hungry view.setPhil(identity,view.HUNGRY); if (identity%2 == 0) { left.get(); // gotleft chopstick view.setPhil(identity,view.GOTLEFT); } else { right.get(); // gotright chopstick view.setPhil(identity,view.GOTRIGHT); } sleep(500); if (identity%2 == 0) right.get(); // eating else left.get(); // eating view.setPhil(identity,view.EATING); sleep(controller.eatTime()); right.put(); left.put(); } } catch (java.lang.InterruptedException e){} } } Súbor: FixedPhilosopher.java Zdroj: http://www.cse.psu.edu/~catuscia/teaching/cg428/Concurrency_applets/concurrency/diners/