Download Procesamiento paralelo con hilos de Java
Document related concepts
no text concepts found
Transcript
CCP Procesamiento paralelo con hilos de Java Tabla de contenidos 1. Soporte para hilos en Java ...................................................................................................... 1 2. Creación y control de un hilo .................................................................................................. 2 2.1. Métodos de creación de un hilo ..................................................................................... 2 2.2. Control de hilos mediante métodos de Thread .................................................................. 2 3. Sincronización por exclusión mutua ......................................................................................... 2 3.1. Sincronización de métodos ........................................................................................... 2 3.2. Sincronización de bloques ............................................................................................ 3 3.3. Sincronización de métodos y bloques estáticos ................................................................. 3 4. Espera y notificación de eventos .............................................................................................. 3 4.1. Wait y notify ............................................................................................................. 3 4.2. NotifyAll .................................................................................................................. 4 4.3. Wait y sleep .............................................................................................................. 4 4.4. Wait y notify en métodos estáticos ................................................................................. 4 5. La integral numérica con hilos ................................................................................................. 4 5.1. Diseño ..................................................................................................................... 4 5.2. La clase NumIntegralTh .............................................................................................. 4 5.3. La clase IntervalTh ..................................................................................................... 5 5.4. La clase IntegralRes .................................................................................................... 6 5.5. Versión en C ............................................................................................................. 6 5.6. Tiempos de ejecución .................................................................................................. 7 Bibliografía .............................................................................................................................7 1. Soporte para hilos en Java En Java el soporte a la concurrencia (paralelismo) basado en hilos se hace en el propio lenguaje y no en base a una librería auxiliar (p.ej. PThreads, hilos win32). El soporte de Java a los hilos se hace mediante los siguientes elementos: • Dotando a la clase ancestral Object de los métodos Wait(), Notify() y NotifyAll() que dan soporte a los eventos • Asociando un lock a cada clase y a cada objeto, la obtención y la devolución del lock se hace mediante la utilización de la palabra reservada synchronized mediante la cual se definen métodos o bloques de código a los que se accede en exclusión mutua • Proporcionando la clase Thread en la librería java.lang que implementa los mecanismos básicos para realizar hilos • La interfaz Runnable permite la creación de hilos en clases que extienden otra clase diferente de Thread • Otras funcionalidades disponibles son: • El control de prioridad • La posibilidad de limitar el número de hilos activos mediante la clase ThreadPool • La posibilidad de controlar grupos de hilos mediante la clase ThreadGroup IV-A.1 Procesamiento paralelo con hilos de Java CCP 2. Creación y control de un hilo 2.1. Métodos de creación de un hilo En Java se puede crear un hilo mediante dos mecanismos: • Extendiendo la clase Thread • Implementado la interfaz Runnable Tanto Thread como Runnable forman parte de java.lang con lo que no requieren ser importados explícitamente. Ambos métodos tienen en común el procedimiento básico que consiste en implementar el método run(). El método para iniciar el hilo depende de la forma como se halla creado: • Extendiendo la clase Thread Se llama al método start de los objetos que instancian la clase • Implementando Runnable Se crea un objeto Thread con el constructor que tiene como parámetro un objeto Runnable y llamando posteriormente al método start de dicho objeto En las páginas 15-23 se dispone de un ejemplo en el que se incluye una animación en un Applet utilizando los dos mecanismos: una clase auxiliar que extiende Thread e implementando Runnable en el propio Applet. 2.2. Control de hilos mediante métodos de Thread La clase Thread contiene una serie de métodos que permiten controlar el funcionamiento de los hilos: • isAlive Devuelve un valor true si el hilo esta activo, es decir su método run ha sido iniciado con start y todavía no ha finalizado • join Condiciona la continuación del hilo actual a la terminación de otro hilo, la duración de esta espera se puede limitar • setName y getName Permiten dar un nombre a un hilo y obtener dicho nombre, respectivamente. Esto tiene interés sobre todo para la depuración. La función setName se puede evitar utilizando los constructores adecuados de Thread (pag 30) También se dispone de los siguientes métodos estáticos: • sleep Detiene la ejecución del hilo que hace la llamada durante el tiempo que se especifica • currentThread Permite identificar al hilo que hace la llamada (ejemplo en la página 31) • enumerate Devuelve una matriz con referencias a todos los hilos creados por el programa (no necesariamente activos) y su número • activeCount Devuelve el número de hilos creados por el programa (no necesariamente activos) 3. Sincronización por exclusión mutua 3.1. Sincronización de métodos La ejecución de métodos de un objeto se puede forzar a que se haga en exclusión mutua utilizando la palabra reIV-A.2 CCP Procesamiento paralelo con hilos de Java servada synchronized en la declaración del método (pag 45) Este mecanismo asegura que la modificación del estado del objeto se realiza de forma consistente evitando las condiciones de carrera ya que solamente un objeto puede estar ejecutando en un momento dado un método sincronizado, o sea que no puede haber simultáneamente dos o mas objetos ejecutando métodos sincronizados (el mismo método o diferentes métodos) de forma simultánea. Esta serialización del acceso a los métodos sincronizados asegura la evitación de condiciones de carrera, aunque por otra parte reduce las posibilidades de paralelismo en la utilización del objeto. El funcionamiento de esto consiste en que al iniciar un método sincronizado se toma el lock asociado al objeto y al finalizar se devuelve el lock dejándolo disponible. 3.2. Sincronización de bloques Con el fin de reducir el efecto de la serialización de código sincronizado sobre el paralelismo, se puede hacer uso de la sincronización de bloque que resulta más flexible: • Permite serializar segmentos de código dentro de un método • Permite elegir el objeto cuyo lock se va a utilizar en la sincronización (no ha de ser necesariamente this) Un ejemplo de sincronización de un bloque aparece la página 55. syncrhonized(obj) { ... ... } Hay que evitar que el objeto cuyo lock se utiliza en synchonized sea null, y también tener cuidado ya que las referencias a objetos pueden cambiar en tiempo de ejecución y por tanto los efectos sobre la sincronización pueden generar errores. 3.3. Sincronización de métodos y bloques estáticos Los métodos estáticos también pueden sincronizarse ya que cada clase tiene asociado también un lock, que en realidad es el lock del objeto de tipo Class asociado a la clase y que puede obtenerse explícitamente mediante: Class classobj = Class.forName("Nombre_de_la_clase"); Los bloques de código dentro de un método estático pueden sincronizarse haciendo uso del objeto mencionado: public class Ejemplo { void proceso() { .... synchronized (Class.forName("Ejemplo")) { ..... } .... } } 4. Espera y notificación de eventos 4.1. Wait y notify El mecanismo de espera y notificación viene proporcionado en Java en la clase ancestra Object, este mecanismo evita la espera ocupada de un hilo respecto de un cambio que ha de producir otro hilo. El primero hace una espera no ocupada (wait) y el segundo una notificación (notify). IV-A.3 Procesamiento paralelo con hilos de Java CCP Ambos métodos han de llamarse desde código sincronizado con el mismo lock para asegurar que el test de la condición y la entrada en espera se realizan en exclusión mutua. La función wait() libera el lock antes de iniciar la espera y retoma el lock después de recibir la notificación. En las páginas 67-68 se tiene una versión de un semáforo con espera ocupada para la operación get. La versión con wait de la página 70 evita la espera ocupada y requiere sincronizar el método. Obsérvese que wait está en un bucle en el que después de salir de wait se vuelve a testear la condición. 4.2. NotifyAll Cuando hay varios hilos en espera de la misma condición no es posible saber cual de ellos recibirá la notificación. El método NotifyAll permite notificar a todos los hilos en espera que han utilizado el mismo lock, será el mismo lock que se ha utilizado para sincronizar el código que incluye la llamada a NotifyAll. En la página 75 se tiene la clase ResourceThrottle como ejemplo de utilización de NotifyAll. Todos los hilos son notificados pero sólo uno de los que demandan un número igual o menor de los recursos disponibles saldrá de la espera. En las páginas 76-77 se tiene la clase TargetNotify que implementa un mecanismo de espera y notificación selectivo. 4.3. Wait y sleep El método wait tiene una versión que permite fijar un timeout en milisegundos, al cabo del cual es hilo retoma la ejecución aunque no se haya producido la notificación. Esta versión de wait funciona como sleep con la diferencia de que wait no retiene el lock, por lo que se puede utilizar dentro de un método sincronizado a diferencia de sleep. 4.4. Wait y notify en métodos estáticos Wait y notify son métodos no estáticos por lo que no se pueden llamar dentro de métodos estáticos. Sin embargo, se pueden utilizar los métodos wait y notify asociados a un objeto estático como en el ejemplo MyStaticClass de la página 86 que ofrece los métodos staticWait y staticNotify que pueden utilizarse en métodos estáticos. 5. La integral numérica con hilos 5.1. Diseño Para hacer la integral numérica con hilos de Java se han utilizado tres clases • La clase NumIntegralTh que es la clase principal con el método main que lanza los hilos • La clase IntervalTh que define cada hilo • La clase IntegralRes que define el acceso al resultado total en exclusión mutua 5.2. La clase NumIntegralTh package ccpthreads; import java.io.*; public class NumIntegralTh { IV-A.4 CCP Procesamiento paralelo con hilos de Java public static void main(String[] args) { double a = 0.0; double b = 0.0; int n = 0; int p = 0; IntervalTh intervalTh[]; IntegralRes res = new IntegralRes(); long t0, t1; BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); System.out.println ("Enter a, b, n and p "); try { Double A = new Double(input.readLine()); a = A.doubleValue(); Double B = new Double(input.readLine()); b = B.doubleValue(); Integer N = new Integer(input.readLine()); n = N.intValue(); System.out.println(n); Integer P = new Integer(input.readLine()); p = P.intValue(); } catch (IOException e) { System.out.println (e.getMessage()); } intervalTh = new IntervalTh[p]; for (int i=0; i<p; i++) intervalTh[i] = new IntervalTh(i, a, b, n, p, res); t0 = System.currentTimeMillis(); for (int i=0; i<p; i++) intervalTh[i].start(); try { for (int i=0; i<p; i++) intervalTh[i].join(); } catch (InterruptedException e) { System.out.println (e.getMessage()); } t1 = System.currentTimeMillis(); System.out.println ("El valor de la integral es "+res.get()); System.out.println ("El tiempo transcurrido es: "+ (t1-t0)); } } 5.3. La clase IntervalTh package ccpthreads; public class IntervalTh extends Thread { int id; double a, b; int n, p; IntegralRes res; public IntervalTh(int id, double a, double b, int n, int p, IntegralRes res) { this.id = id; this.a = a; this.b = b; this.n = n; this.p = p; this.res = res; } private double f(double x) { double y; y = Math.log(Math.sin(x*x+1)); y = Math.sqrt(y*y); return y; } private double Trap (double a, double b, int n, double h) { double integral; double x; int i; integral = (f(a) + f(b)) / 2.0; x = a; for (i=1; i<n; i++) { x = x + h; integral = integral + f(x); } integral = integral*h; IV-A.5 Procesamiento paralelo con hilos de Java CCP return integral; } public void run() { double h; double local_a; double local_b; int local_n; double integral; h = (b - a)/(double)n; local_n = n / p; local_a = a + (double)id * local_n * h; local_b = local_a + local_n * h; integral = Trap (local_a, local_b, local_n, h); res.update(integral); } } 5.4. La clase IntegralRes package ccpthreads; public class IntegralRes { private double total; public IntegralRes() { total = 0; } public synchronized void update (double v) { total = total + v; } public synchronized double get() { return total; } } 5.5. Versión en C #include <stdio.h> #include <time.h> #include <math.h> double f(double x) { double y; y = log(sin(x*x+1)); y = sqrt(y*y); return y; } int main(int argc, char* argv[]) { double integral; float a; float b; int n; double h; double x; int i; clock_t t0, t1; float t; a = 0.0; b = 0.0; n = 0; printf ("Enter a, b and n\n"); scanf ("%f %f %d", &a, &b, &n); printf ("of the integral from %f to %f\n", a, b); t0 = clock(); h = ((double)b-(double)a)/(double)n; integral = (f(a) + f(b)) / 2.0; x = a; for (i=1; i<n-1; i++) { x = x + h; integral = integral + f(x); IV-A.6 CCP Procesamiento paralelo con hilos de Java } integral = integral * h; t1 = clock(); t = (float)((1000*t1)/CLOCKS_PER_SEC) - (float)((1000*t0)/CLOCKS_PER_SEC); printf ("With n = %d trapezoids, our estimate\n", n); printf ("of the integral from %f to %f = %20.20f\n", a, b, integral); printf ("Tiempo = %f\n", t); return 0; } 5.6. Tiempos de ejecución Tabla 1. a = 0.0, b = 1.0, n = 10000000, p = 4 Sistema Secuencial (C) Secuencial (Java) Paralelo Origin 2000 7770 19588 4793 Athlon 1.7GHz linux 3620 1774 ? Bibliografía Scott Oaks, Henry Wong "Java Threads" O'Reilly Doug Lea "Concurrent Programming in Java: Design Principles and Patterns" Addison-Wesley IV-A.7