Come esempio di problema di calcolo prendiamo la ricerca del minimo valore presente in un vettore.
Questo problema (come molti altri) si presta molto bene ad essere elaborato in parallelo: calcolo separatamente il minimo del primo e del secondo mezzo vettore e poi ricombino i risultati.
v è un vettore di n elementi m1 = calcolo il minimo di v dalla posizione 0 alla posizione n/2 m2 = calcolo il minimo di v dalla posizione n/2+1 alla posizione n-1 minimo = minimo tra m1 e m2
Minimo
Ci ricordiamo tutti come si calcola il minimo valore presente in un vettore vero? Ci aiuterà nella successiva trattazione scrivere una funzione per calcolare il minimo:
public int minimo(int v[]){ int min = v[0]; for(int i=1; i<v.length; i++){ if(v[i]<min){ min = v[i]; } } return min; }
In effetti a noi fa comodo una sua (poco significativa dal punto di vista algoritmico) variazione:
public int minimo(int v[], int primo, int ultimo){ int min = v[primo]; for(int i=primo+1; i<=ultimo; i++){ if(v[i]<min){ min = v[i]; } } return min; }
In pratica abbiamo solo reso esplicito quale parte del vettore va considerata, questo ci farà comodo per dividere il problema in due parti.
Minimo parallelo
Da qui in avanti sono tutti dettagli implementativi per trattare con le clasi che Java ci mette
a disposizione, potremmo usare diversi approcci ma per semplicità adesso usiamo la classe
java.lang.Thread
e senza troppe ottimizzazioni la classe che avvia i due thread una volta avviati aspetterà che
questi completino il lavoro prima di andare avanti.
Iniziamo dalla classe che calcola il minimo: questa classe estende Thread
e definisce tre metodi:
- Cercatore
- è il costruttore e semplicemente richiede che per creare un Cercatore serve di sapere su quale vettore lavorare e anche su quale intervallo di posizioni.
- run
- questo metodo è quello che fa tutto il lavoro, quando termina il thread stesso viene chiuso
- getMinimo
- è il metodo che ci fornisce il risultato... ovviamente va chiamato quando il thread ha finito il suo lavoro, chiamarlo prima non avrebbe senso e produrrebbe un risultato sbagliato.
public class Cercatore extends Thread { private int inizio, fine; private int[] sequenza; private int minimo; public Cercatore(int inizio, int fine, int[] sequenza) { this.inizio = inizio; this.fine = fine; this.sequenza = sequenza; } @Override public void run(){ minimo=sequenza[inizio]; for(int i = inizio ; i < fine ; i++){ if(sequenza[i] < minimo){ minimo=sequenza[i]; } } } public int getMinimo(){ return minimo; } }
Gestore dei thread
Bene, adesso abbiamo un lavoratore che cerca il minimo... e magari abbiamo un processore con più di un nucleo di elaborazione: come sfruttiamo la situazione? Costruendo un programma che usa il Cercatore!
public class MinimoNumero { public static void main(String[] args) throws InterruptedException{ int sequenza[] = new int[10_0000_000]; // carico una sequenza casuale, per provare for(int i=0; i < sequenza.length; i++) { sequenza[i] = (int)(Math.random() * 10000000); } //Creo i Threads Cercatore cercatore1 = new Cercatore(0, 49999999, sequenza); Cercatore cercatore2 = new Cercatore(50000000, 99999999, sequenza); // li avvio cercatore1.start(); cercatore2.start(); System.out.println("Threads avviati!"); // il metodo join dell'oggetto Thread mette il thread corrente in attesa // finché il cercatore1 non termina. cercatore1.join(); // attendo la fine anche del secondo thread cercatore2.join(); System.out.println("Lavoro finito"); // recupero i risultati int minimoThread1 = cercatore1.getMinimo(); int minimoThread2 = cercatore2.getMinimo(); if(minimoThread1 < minimoThread2) { System.out.println(minimoThread1); }else { System.out.println(minimoThread2); } } }
Per chiarezza dobbiamo precisare che: questo programma potrebbe essere scritto in maniera più furba! (magari senza bloccarsi) e che un processore attuale lo esegue in tempi brevissimi. Lo stesso identico schema può però ad esempio essere usato per ruotare una scena 3D (sempre conti sono!), in questo caso avere 4 nuclei (o più) di elaborazione e sfruttarli bene fa la differenza.