Programowanie wielowątkowe: podstawowe koncepcje, narzędzia w Javie J. Starzyński, JiMP2, rok akad. 2005/2006
Tematyka Wprowadzenie Podstawowe pojęcia Tworzenie i uruchamianie wątków Zatrzymywanie wątków Współdzielenie pamięci synchronizacja Komunikacja pomiędzy wątkami Hierarchie i priorytety wątków
Wprowadzenie Większość programów działa w środowiskach sekwencyjnych, gdzie procesor wykonuje ciąg instrukcji jedna po drugiej. Wiele programów wymaga jednak równoległego wykonywania więcej niż jednego ciągu operacji. Taką równoległość symuluje się przez cykliczne przełączanie procesora.
Wprowadzenie, c.d. Proces to samodzielnie wykonujący się program z własną przestrzenią adresową. Wielozadaniowy system operacyjny jest w stanie uruchomić jednocześnie wiele procesów. Każdy z nich może działać niezależnie od innych, a system przydziela każdemu pewien przedział czasu procesora. Wątek to niezależne podzadanie w ramach procesu. Może współdzielić pamięć z innymi wątkami.
Wprowadzenie, c.d. W Javie można tworzyć wątki jako rozdzielne sekwencje strowania. Do czego się to przydaje? sterowanie obsługa urządzeń programowanie grafiki interakcyjnej programowanie równoległe i rozproszone while( 1 ) { t1= gettemperature( sensor1 ); t2= gettemperature( sensor2 ); t3= gettemperature( sensor3 ); p1= getpressure( sensor5 ); p2= getpressure( sensor6 ); if( t1 > TMAX t2 > TMAX ) stopheater( h1 ); if( t1 < t2 ) switchheater( h2 ); if( t3 > TMAX && p1 > PMAX ) openvalve( );
Wprowadzenie, c.d. Model odsłuchowy while( n_nodes > 4 ) { check_sharp_edges( &n_nodes, nodes, x, y, np, nop, ne ); if( n_nodes < 5 ) break; if( userinput() ) /* sprawdzamy, czy uzytkownik sie nie znudzil */ break; find_edge_seq( &n_nodes, nodes, x, y, np, nop, ne, &l_seq, &ptr ); if( userinput() ) /* sprawdzamy, czy uzytkownik sie nie znudzil */ break; if( l_seq > 1 ) close_edge_seq( &n_nodes, nodes, x, y, np, nop, ne, &l_seq, &ptr ); else catalizer( &n_nodes, nodes, x, y, np, nop, ne ); if( n_nodes < 5 ) break; if( userinput() ) /* sprawdzamy, czy uzytkownik sie nie znudzil */ break; if( userinput() ) /* uzytkownik sie znudzil */ processuserinput();
Wprowadzenie, c.d. Model zdarzeniowy int kbd_listener( int key_pressed ) { if( key_pressed == CTRL_C ) ask_if_really_stop(.); register_listener( kbd_listener ); while( n_nodes > 4 ) { check_sharp_edges( &n_nodes, nodes, x, y, np, nop, ne ); if( n_nodes < 5 ) break; find_edge_seq( &n_nodes, nodes, x, y, np, nop, ne, &l_seq, &ptr ); if( l_seq > 1 ) close_edge_seq( &n_nodes, nodes, x, y, np, nop, ne, &l_seq, &ptr ); else catalizer( &n_nodes, nodes, x, y, np, nop, ne );
Tworzenie wątków metoda A Klasę-wątek możemy utworzyć rozszerzając szkieletową klasę Thread Jeżeli wątek ma coś robić, to musimy w naszej klasie utworzyć metodę public void run() Ta metoda jest uruchamiana przez wywołanie metody start. Wątek,,żyje tak długo, jak długo działa jego metoda run
Tworzenie wątków przykład A class W1 extends Thread { String name; long delay; long nrepet; W1( String w, long dt, long n ) { name= w; delay= dt; nrepet= n; public void run() { try { for( int i= 0; i < nrepet; i++) { System.out.print( name + " " ); sleep( delay ); catch( InterruptedException 3 e ) { System.out.println( "Exiting " + name + "\n" ); return;
Tworzenie wątków przykład A, c.d. class W1Test { public static void main( String args[] ) throws InterruptedException { for( int i= 0; i < args.length; i += 3 ) new W1( args[i], Long.valueOf( args[i+1] ).longvalue(), Long.valueOf( args[i+2] ).longvalue() ).start();
Tworzenie wątków metoda B Klasę-wątek możemy utworzyć implementując interfejs Runnable Pozwala to utworzyć klasę-wątek, która rozszerza jakąś klasę, która nie jest wątkiem Podobnie, jak w metodzie A musimy w naszej klasie utworzyć metodę public void run() Klasę implementującą Runnable uruchamiamy w nieco bardziej skomplikowany sposób
Tworzenie wątków przykład B class W2 implements Runnable { String name; long delay; long nrepet; W2( String w, long dt, long n ) { name= w; delay= dt; nrepet= n; public void run() { try { for( int i= 0; i < nrepet; i++) { System.out.print( name + " " ); Thread.sleep( delay ); catch( InterruptedException e ) { System.out.println( "Exiting " + name + "\n" );
Tworzenie wątków przykład B, c.d. class W2Test { public static void main( String args[] ) throws InterruptedException { for( int i= 0; i < args.length; i += 3 ) { Runnable r= new W2( args[i], Long.valueOf( args[i+1] ).longvalue(), Long.valueOf( args[i+2] ).longvalue() ); new Thread( r ).start();
Zatrzymywanie wątków Program działa, dopóki działa choć jeden z jego wątków (z wyj. wątków-demonów). Każdy program ma przynajmniej jeden wątek jest nim metoda main uruchamianej klasy Wątek można zatrzymać komunikując się z jego metodą run tak jest najlepiej Można też zatrzymać wątek wysyłając mu przerwanie (wyjątek InterruptException) W starej Javie można też było wywołać metodę stop (zgłasza wyjątek ThreadDeath), ale jest to sposób przestarzały (niebezpieczny)
Zatrzymywanie wątków class Wx extends Thread { public boolean canrun; public void start() { canrun= true; super.start(); public void run() { while( canrun ) { // sprzątanie class WxUser { Wx t= new Wx(); t.start(); if( t_should_stop ) t.canrun = false;
Możliwe stany wątku Utworzony, ale nie uruchomiony Uruchomiony Zakończony (koniec run(), stop(), destroy()) Zablokowany sleep() suspend(). resume() yield() wait() we/wy czeka na zwolnienie monitora
Problem: dzielony dostęp do danych Wątek Kasa: stankonta = stankonta + wpłata; Wątek Bankomat: stankonta = stankonta - wypłata; stankonta rejestr stankonta rejestr rejestr += wpłata rejestr -= wpłata rejestr stankonta rejestr stankonta Stan konta nie uwzględnia wpłaty!
Synchronizacja Każdy obiekt zawiera monitor blokadę, która jest automatycznie jego częścią. Można wykorzystać ją (po to jest) do blokowania obiektu. Pierwszym sposobem jest deklarowanie metod, które powinny blokować obiekt jako synchronized Drugim sposobem jest użycie bloków synchronizowanych
Synchronizacja, c.d. class SP { private double x,y; SP( ) { x= 0; y= 0; public synchronized double x() { return x; public synchronized double y() { return y; public synchronized void move( double dx, double dy ) { x += dx; y += dy; public synchronized void newx( double x ) { this.x = x; public synchronized void newy( double y ) { this.y = y; public synchronized void movetoward( SP other, double a ) { this.x += ( other.x() - this.x ) * a; this.y += ( other.y() - this.y ) * a;
Synchronizacja, c.d. class SP { private double x,y; SP( ) { x= 0; y= 0; public synchronized double x() { return x; public synchronized double y() { return y; public synchronized void move( double dx, double dy ) { x += dx; y += dy; public synchronized void newx( double x ) { this.x = x; public synchronized void newy( double y ) { this.y = y; public synchronized void movetoward( SP other, double a ) { this.x += ( other.x() - this.x ) * a; this.y += ( other.y() - this.y ) * a;
Synchronizacja, c.d. Trzeba pamiętać o synchronizowaniu wszystkich metod, które mają dostęp do wrażliwych pól. Metoda niesynchronizowana nie sprawdza blokady. Każda klasa ma jeden monitor statyczny, który jest używany do synchronizowania metod statycznych. Synchronizowanie spowalnia program: czas wywołania metod synchronizowanych jest kilkakrotnie (B.Eckel 4 razy) dłuższy, niż metod niesynchronizowanych.
Blok synchronizowany synchronized ( syncobject ) { // kod synchronizowany public synchronized void run () { while( true ) { counter++; l1.settext( Integer.toString( counter ); try { sleep( delay ); catch( InterruptedException e ) { System.err.println( name + interrupted ); Synchronizacja, c.d. public void run () { while( true ) { synchronized( this ) { counter++; l1.settext( Integer.toString( counter ); try { sleep( delay ); catch( InterruptedException e ) { System.err.println( Interrupted ); /* Move polygon by [ dx, dy ] */ public static void move( Point[] polygon, double dx, double dy ) { synchronized( polygon ) { for( int i= 0; i < polygon.length; i++ ) polygon[i].move( dx, dy );
Synchronizacja -- zakleszczenie Wątek A p1.movetoward( p2, 0.25 ) blokada p1 Wątek B p2.movetoward( p1, 0.2 ) blokada p2 oczekiwanie na p2.. oczekiwanie na p1. Zakleszczenie może mieć postać bardziej złożoną zamkniętego łańcucha wątków czekających jeden na drugi. Java nie zawiera narzędzi, które pomagałyby unikać zakleszczenia trzeba odpowiednio projektować program. Metody stop(), suspend(), resume() są niezalecane właśnie ze względu na ryzyko zakleszczenia przy ich stosowaniu.
Obiekty volatile class Test { static int i = 0, j = 0; static void one() { i++; j++; static void two() { System.out.println("i=" + i + " j=" + j); class Test { static int i = 0, j = 0; static synchronized void one() { i++; j++; static synchronized void two() { System.out.println("i=" + i + " j=" + j); class Test { static volatile int i = 0, j = 0; static void one() { i++; j++; static void two() { System.out.println( "i=" + i + " j=" + j); percentdone= 0; while( true ) { g.drawstring( Double( percentdone ).tostring() + "%", 50,25 ); Thread.sleep( 500 );
Komunikacja między wątkami Wykorzystujemy do niej monitor obiektu i metody wait(), notify(), notifyall() Metody wait(), notify(), notifyall() powinny być wywoływane z kodu synchronizowanego, dla swojej własnej blokady(). wait() zwalnia blokadę obiektu i czeka na wywołanie notify() lub notifyall() związane z tą blokadą Są wersje wait() odmierzające czas i czekające bezwarunkowo. notify() budzi jeden z wątków (niewiadomo który) czekających na monitor danego obiektu. Metody zgłaszają IllegalMonitorStateException, jeśli wątek wywołujący nie jest właścicielem blokady (monitora).
Synchronizacja w czasie public class IndividualFitness extends Thread { // tu definicja klasy public class GA { // public void generation() { oldpopulation= newpopulation; IndividualFitness [] FitPool= new IndividualFitness[ populationsize ]; for( int i= 0; i < populationsize; i++ ) { newpopulation[i]= new Individual( oldpopulation ); FitPool[i]= new individualfitness( newpopulation[i] ); FitPool[i].start(); for( int i= 0; i < populationsize; i++ ) { FitPool[i].join(); newpopulation[i].setfitness( FitPool.result() ); populationstatistics( newpopulation ); //.. //..
Synchronizacja w czasie import java.util.stack; public class SQueue { private Stack q; public SQueue() { q = new Stack(); public synchronized void insert( Object o ) { q.push( o ); notify(); // powiadom watek pobierajacy public synchronized Object get() throws InterruptedException // z wait() { while( q.empty() ) wait(); // czekaj az element sie pojawi return q.pop();
Synchronizacja w czasie class SQFeeder extends Thread { int start; int delay; int nrepet; SQueue sq; SQFeeder( int s, int n, int dt, SQueue q ) { start= s; delay= dt; nrepet= n; sq= q;. public void run() { try { for( int i= start; i < start+nrepet; i++) { sq.insert( new Integer( i ) ); sleep( delay ); catch( InterruptedException e ) { System.out.println( "SQFeeder interrupted\n" ); return;
Synchronizacja w czasie class SQEater extends Thread { SQueue sq; SQEater( SQueue q ) { sq= q; public void run() { try { while( true ) System.out.println( sq.get() ); catch( InterruptedException e ) { System.out.println( "SQEater interrupted\n" ); return;
Synchronizacja w czasie public class SQTester { public static void main( String [] args ) throws InterruptedException // ze sleep() { SQueue q= new SQueue(); SQFeeder f= new SQFeeder( 0, 20, 250, q ); SQEater e= new SQEater( q ); f.start(); // start feeder Thread.sleep( 4000 ); // wait 4 s. e.start(); // start eater - it should print 15-0 immediately, // then 16-19 with 250 ms. pauses f.join(); // wait for feeder to finish e.interrupt(); // finish eater e.join(); // let him stop
Priorytety wątków Każdy wątek ma przyporządkowany określony priorytet - liczbę całkowitą z zakresu od Thread.MIN_PRIORITY do Thread.MAX_PRIORITY i standardową wartością Thread.NORM_PRIORITY. Początkowo wątek dziedziczy priorytet od swojego rodzica. Wartość priorytetu można odczytać przy pomocy metody getpriority i zmienić w dowolnej chwili przy pomocy setpriority. Priorytety służą do szeregowania wątków.
public static void setthreadspriority( ) { Priorytety wątków Thread tenwatek= Thread.currentThread(); // pobierz biezacy watek ThreadGroup tagrupa= tenwatek.getthreadgroup(); // i jego grupe Thread[] watki = new Thread[ tagrupa.activecount() + 50 ]; // activecount tylko estymuje (cos moglo umrzec i cos sie urodzic) tagrupa.enumerate( watki ); // pakujemy watki do tablicy tenwatek.setpriority( Thread.MAX_PRIORITY ); // mogl nie dostac MAX_PRIORITY, bo ograniczenia dla grupy to uniemozliwialy int otherpriority= tenwatek.getpriority() - 1; // ustawiamy innym mniejszy for( int i= 0; i < watki.length; i++ ) if( watki[i]!= null && watki[i]!= tenwatek ) watki[i].setpriority( otherpriority ); // ale potem cos innego mogloby zwiekszyc priorytet ktoregos z tych watkow //skuteczniej: tagrupa.setmaxpriority( otherpriority );
Grupy wątków Wątki podzielone są na grupy, które tworzą hierarchie (to znaczy, że wątek dziedziczy grupę od rodzica, ale może tworzyć nowe podgrupy w ramach swojej grupy). Do tworzenia hierarchii wątków służy klasa ThreadGroup. Obok regulacji bezpieczeństwa klasa ta umożliwia grupowe sterowanie wątkami i ich priorytetami.
Grupy wątków public static void stopthreads( ) { Thread tenwatek= Tread.currentThread(); // pobierz biezacy watek ThreadGroup tagrupa= tenwatek.getthreadgroup(); // i jego grupe Thread[] watki = new Thread[ tagrupa.activecount() + 50 ]; // activecount tylko estymuje (cos moglo umrzec i cos sie urodzic) tagrupa.enumerate( watki ) // przerywamy wszystkie z wyjatkiem biezacego for( int i= 0; i < watki.length; i++ ) if( watki[i]!= null && watki[i]!= tenwatek ) watki[i].interrupt(); // czekamy az stana for( int i= 0; i < watki.length; i++ ) if( watki[i]!= null && watki[i]!= tenwatek ) { try { watki[i].join(); // join zglasza InterruptedException catch( InterruptedException e ) { // zlekcewaz - czekamy na "potomstwo" // zostal tylko tenwatek