Multiprocesamiento en Java: ¡Dale caña a tu procesador!

2012-11-17 - Categorías: General / Java

Multiprocesamiento, como reza la Wikipedia, se refiere a la ejecución de varios procesos de manera concurrente, es decir, a la vez. Con los nuevos procesadores que cada vez nos traen más núcleos, he visto que ésto viene a ser más importante. Con Java el multiprocesamiento para aprovechar los núcleos de uno o varios procesadores viene gestionado con la máquina virtual de Java. Es decir, sólo tendremos que preocuparnos de hacer el programa de manera que se puedan ejecutar ‘a trozos’, poner cada tarea en un hilo de ejecución y luego la máquina virtual se encargará de gestionarlo todo.

Ésto es lo mismo que viene ocurriendo con los Sistemas Operativos (SO). Tenemos muchos procesos que se ejecutan desde que arrancamos el ordenador, y el SO se encarga de gestionar qué se ejecuta en cada momento. De igual manera nosotros en Java podemos gestionar qué se ejecuta o que se queda esperando, también podemos decirle a todas las tareas que se ejecuten a la vez simplemente sin esperar unas a otras ni nada parecido. Ésto es lo que vamos a ver con el ejemplo sencillo de a continuación, donde se van a crear dos tareas, a y b, y se van a ejecutar concurrentemente.


Tenemos éste código:

// package variado;

// ESTE EL PROGRAMA PRINCIPAL ----
public class manejadorDeHilos {
    public static void main(String[] args) {
        unaTareaEnUnHilo a, b;
        a = new unaTareaEnUnHilo();
        b = new unaTareaEnUnHilo();
        a.start();
        b.start();
    }
}
 
// FIN DEL PROGRAMA PRINCIPAL ----
 
// Éste es un objeto que representa
// una tarea que se ejecuta en un hilo
class unaTareaEnUnHilo extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print(i + ", ");
        }
    }
}

La clase manejadorDeHilos es una objeto sencillo que crea dos variables a y b, las dos del mismo tipo. El meollo del asunto está programado con la clase unaTareaEnUnHilo, que extiende de la clase Thread, la cual significa hilo en castellano. 

Tenemos entonces que hacer las tareas en una clase que extienda de Thread para que se ejecute en un hilo. Y a su vez tenemos que programar el método run(), que es el que se ejecuta cuando el hilo arranca. Las variables del tipo unaTareaEnUnHilo tienen un método propio de la clase Thread que es start(), con el cual le decimos que comience su ejecución, el cual lo que hace es simplemente ejecutar la función run() como en el ejemplo.

Lo copiamos y pegamos en un fichero llamado manejadorDeHilos.java en nuestro IDE favorito, lo ejecutamos, y a mi por ejemplo me escribe lo siguiente por la salida estándar:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 0, 1, 2, 3, 4, 5, 6, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 7, 8, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 83, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 91, 95, 92, 96, 93, 94, 95, 96, 97, 98, 99, 97, 98, 99,

Vamos a ver qué ha pasado. ¿Porqué no se ha ejecutado la tarea a y luego la b? ¿porqué están desordenados los números?

Las dos tareas se han ejecutado a la vez, y cuando han podido han ido escribiendo el número por el que iban contando. Si ejecutamos varias veces el programa veremos que da resultados distintos.

En líneas generales ésto es lo mínimo necesario para empezar con los hilos de Java.  Luego podemos interrumpir con la función interrupt() o continuar la ejecución de un hilo con resume(). También podemos poner a dormir un hilo con sleep(). Para jugar un poco con los hilos podríamos crear un vector de hilos y que el hilo 1 hiciera dormir al 2 y cuando haya terminado de ejecutar su tarea que corra el 2. Y de paso un tercer hilo que se ejecute contínuamente.

Entonces, ¿para qué todo ésto de los hilos? Voy a seguir con el mismo ejemplo, se me ha ocurrido cambiar la tarea unaTareaEnUnHilo de la manera siguiente:

class unaTareaEnUnHilo extends Thread {
    public void run() {
        for (int i = 0; i < 100000000; i++) {
            for (int j = 0; j < 100000000; j++) {
                for (int k = 0; k < 100000000; k++) {
                    for (int l = 0; l < 100000000; l++) {

                    }
                }
            }
        }
    }
}

Ahora dirás, ¡ostras! ¡cuántos bucles! Ahora va a tardar mucho cada hilo, y esta vez sí que se va a notar si se han usado los hilos o no en el tiempo total que va a tardar, aunque tal vez tarde demasiado y haya que pararlo antes… Entonces podemos ver cómo se nota la ejecución con el visor de los núcleos del PC. Al ejecutar el programa con las modificaciones, el administrador de tareas, muestra lo siguiente de mi procesador:

Se puede ver claramente que los núcleos de mi procesador son 4, y de los cuáles el segundo y tercero son los que están ejecutando las tareas a y b. Y mi procesador va al 50% aproximadamente. Si pusiéramos dos tareas más con hilos subiría al 100% y aprovecharíamos el procesador bien. Podemos modificar el ejemplo anterior cambiando sólo el manejadorDeHilos para verlo al 100%:

public class manejadorDeHilos {
    public static void main(String[] args) {
        unaTareaEnUnHilo a, b, c, d;
        a = new unaTareaEnUnHilo();
        b = new unaTareaEnUnHilo();
        c = new unaTareaEnUnHilo();
        d = new unaTareaEnUnHilo();
 
        a.start();
        b.start();
        c.start();
        d.start();
    }
}

Si no hubiera utilizado los hilos, para ejecutar las dos tareas, el procesador hubiera ido al 25% más o menos y hubiera tardado bastante más en terminar. Se hubieran ejecutado primero una, luego la otra, y los números hubieran salido todos ordenados por la pantalla. ¿Qué pasa entonces cuando tengo muchas tareas? no sólo dos o cuatro. Pues ya es otro tema, podemos hacer un vector de tareas, una cola de tareas, una lista o lo que sea que se nos pueda ocurrir. Imaginemos pues qué haríamos si quisiéramos hacer unas 3000 tareas, mejor si está programado cada una en un hilo, y se lanzan todos los hilos, seguro que el procesador terminaría antes y se pondría a echar humo.

Así que pues, a darle caña a nuestro procesador xD

Un saludo.

4 respuestas a “Multiprocesamiento en Java: ¡Dale caña a tu procesador!”

  1. Manuel dice:

    Hola:
    De su página infiero que los hilos en java se ejecutan en distintos núcleos. Es decir, que la JVM se encarga de distribuir los hilos en diferentes núcleos. ¿Es correcta mi inferencia? Pensaba que los hilos de un mismo proceso se ejecutaban en un mismo núcleo, pero al parece la JVM nos ayuda en eso.

    • Jnj dice:

      Hola Manuel.
      Cierto, cada hilo se ejecuta en distintos núcleos.
      Al parecer la JVM lanza procesos nuevos con su PID independiente, con lo que acaban ocupando núcleos distintos.
      Quizá no habría que llamarlos hilos si no procesos.

      • Manuel dice:

        Gracias por responder.

        He estado probando con la JVM 8 y con la 10.01. Al parecer la 10 es la que ejecuta lo hilos en diferentes núcleos y la 8 no. Pero no estoy seguro. Ejecuté un código que crea un hilo por cada iteración de un ciclo. En una pc con un micro I5 (dos núcleos) y con java 8, el código deja a la PC sin rcursos. Mientras que en un I3 con java 10, todo funciona bien.

        Si tienes documentación oficial del JVM que comente sobre este tema, te agradecería compartir.

        Saludos.

        • Jnj dice:

          Hola Manuel.
          De nada, gracias a ti por comentar. Has revivido este post de 2012 comentándolo 👍
          No recuerdo en qué PC probé el script ni la versión de Java. Te puedo decir que ahora en un PC con 8 núcleos con la versión 11 de OpenJDK funciona haciendo procesos que corren independientes. Hay documentación oficial en estas direcciones:

          https://cr.openjdk.java.net/~iris/se/11/latestSpec/api/java.base/java/lang/Thread.html
          https://docs.oracle.com/javase/tutorial/essential/concurrency/runthread.html

          Citan una clase Runnable que viene a hacer lo mismo según la documentación. De todas formas con este script nuevo a fecha de hoy sigue funcionando:

          // ESTE EL PROGRAMA PRINCIPAL —-
          public class threadsTest {
          public static void main(String[] args) {
          int kmax = 7;
          unaTareaEnUnHilo a[] = new unaTareaEnUnHilo[kmax];

          for (int i = 0; i < kmax; i++) { a[i] = new unaTareaEnUnHilo(); a[i].start(); } } } // FIN DEL PROGRAMA PRINCIPAL ---- // Éste es un objeto que representa // una tarea que se ejecuta en un hilo class unaTareaEnUnHilo extends Thread { public void run() { for (int i = 0; i < 100000000; i++) { for (int j = 0; j < 100000000; j++) { for (int k = 0; k < 100000000; k++) { for (int l = 0; l < 100000000; l++) { } } } } } } Creo que conviene no ocupar todos los núcleos, para evitar colapso del sistema. Si tenemos 4 núcleos usar 3 para el programa, si tenemos 8 entonces 7 para el programa. Pero esto que te digo ya son elucubraciones mías..

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

 

© 2022 JnjSite.com - MIT license

Sitio hecho con WordPress, diseño y programación del tema por Jnj.