lunes, 10 de marzo de 2014

Multihilamientos

1.2 Hilos en el ambiente de programación de Java

El ambiente de desarrollo de Java soporta programación con múltiples hilos por medio de bibliotecas, el lenguaje mismo y con la ayuda del sistema de tiempo de ejecución. A continuación se listan las características más importantes con las que cuenta Java para soportar el uso de hilos.
El método run.
Antes que nada, necesitamos proveer a cada hilo con un método run para indicarle qué debe hacer. El código de este método implementa el comportamiento en ejecución del hilo y puede hacer, prácticamente cualquier cosa capaz de ser codificada en Java. Existen dos técnicas para proveer un método run para un hilo:
  • Haciendo una subclase de Thread y sobrecargando run
  • Implementando la interface Runnable. Aquí cabe mencionar, que el autor del libro de texto (Jim Farley) incurre en un error al mencionar que Java provee dos clases para soportar hilos (página 84, segundo párrafo); ya que Thread es una clase, pero Runnable es una interface.
Tenemos buenas razones para utilizar cada una de estas técnicas. Sin embargo, a manera de regla empírica: ``Si las clases que elaboras deben ser subclases de otras clases (un caso común es Applet), entonces debe usarse Runnable.''
El ciclo de vida de un hilo.
Una vez que el hilo hace algo, podemos manejar el ciclo de vida de un hilo: como crear y arrancar un hilo, algunas cosas especiales que podemos hacer mientras se ejecuta y como detenerlo. En el siguiente diagrama podemos ver los estados en los que un hilo puede estar durante su vida.


  
Figure: Ciclo de vida de un hilo de Java, (esta gráfica es cortesía de The Java Tutorial [5]).
\includegraphics{ciclo.ps}

Prioridades.
La gran ventaja que ofrecen los hilos (como se puede inferir de lo anteriormente expuesto) es que corren de manera concurrente. Conceptualmente, esto es cierto, pero en la práctica generalmente no es posible. La mayoría de las computadoras tienen un sólo procesador, por lo tanto los hilos corren uno a la vez de forma tal que proveen la ilusión de concurrencia (esto es llamado scheduling). El sistema de ejecución de Java soporta un algoritmo determinístico (para el scheduler) llamado fixed priority scheduling. Este algoritmo asigna tiempo de ejecución a un hilo basado en su prioridad relativa a los demás hilos que están listos para ejecutarse. Cuando se crea un nuevo hilo, hereda su prioridad del hilo que lo crea, ésta puede ser modificada con el método setPriority. Las prioridades son enteros entre MIN_PRIORITY y MAX_PRIORITY (constantes definidas en la clase Thread). Entre más alto el entero, más alta la prioridad. Si dos hilos con la misma prioridad están esperando que el CPU los ejecute, el scheduler escoge uno utilizando round-robin (i.e. escoge uno aleatoriamente, se supone que round-robin ofrece iguales probabilidades de ejecución a los hilos en cuestión). El hilo escogido para ejecución, corre hasta que alguna de estas condiciones sea verdadera:
  • Un hilo con mayor prioridad está listo para ejecución.
  • El hilo cede su lugar (yields), o su método run termina.
  • En sistemas que soportan rebanadas de tiempo (time slicing), su tiempo asignado ha expirado.
En ese momento el segundo hilo es atendido por el CPU y así sucesivamente hasta que el intérprete termina.
El algoritmo del scheduler es preemptive. Si en cualquier momento un thred con mauyor prioridad que los demás está listo para ser ejecutado, el sistema de ejecución de Java lo escoge para ser ejecutado. Entonces decimos que el nuevo hilo (de mayor prioridad) desaloja (domina, toma la primera posición, etc.) a los demás.
La prioridad de los hilos debe ser utilizada sólo para modificar la política de asignación del scheduler (por cuestiones de eficiencia) y no para manejar la correctez de un programa. Esto debido a que el scheduler puede decidir ejecutar a un hilo con menor prioridad para evitar inanición.
Sincronización.
Cuando tenemos varios hilos, muchas veces deseamos que éstos pueden compartir datos, por lo tanto, es indispensable saber sincronizar sus actividades; ésto también nos permitirá evitar inanición y abrazos mortales. En muchas situaciones, hilos que se ejecutan concurrentemente comparten información y deben considerar el estado de las actividades de los demás hilos. Un modelo muy común de tales conjuntos de hilos es conocido como escenarios de productor/consumidor, donde el productor genera un flujo de datos que es consumido por un consumidor.
Los segmentos de código dentro de un programa que accesan el mismo objeto desde hilos separados (concurrentes) se llaman regiones críticas. En Java, una sección crítica puede ser un bloque o un método (el autor del libro de texto (pp. 92, párrafo 5), menciona que el método o bloque de código es sincronizado sobre una clase, un objeto o arreglo, lo cual es cierto; sin embargo durante la primera exposición hubo gran discusión en la clase, porque no quedaba claro como sincronizar un objeto por ejemplo. La respuesta es clara ahora: ¡no hay tal cosa como sincronizar un objeto!, sólo bloques de código o métodos pueden ser sincronizados y -como efecto lateral- una clase, un objeto o un arreglo adquieren un candado -lock. Éste candado lo asigna Java a todo objeto que tenga código sincronizado y es utilizado para sincronizar a todos los hilos que utilicen ese ``código'' y evitar así situaciones de concurso. La discusión en clase fue provocada por un mal entendido.)
Java provee algunos métodos más para coordinar las actividades de los hilos, como notifyAll, wait, etc., pero algunos sólo son mencionados en el texto (¿Debido a los ejemplos de juguete -no distribuídos, por cierto?) y su función es evidente, por lo cual támpoco entraremos en detalle aquí.
Agrupamientos de hilos.
Este tema sólo recibe dos párrafos en el capítulo. De hecho, el autor da a entender que podemos -si queremos- agrupar hilos para formar una jerarquía, quedando implícito el hecho ``si queremos''. La realidad no es así, todo hilo en Java es miembro de un grupo. Un grupo de hilos nos da las herramientas para meter varios hilos en un sólo objeto y manipularlos (a todos) al mismo tiempo (a esto le llama: ``comodidad''). La clase ThreadGroup implementa los grupos de hilos en Java. El sistema de ejecución de Java pone a un hilo en un grupo durante la construcción del hilo. Cuando se crea un hilo, uno puede escoger el grupo al que pertenecerá o permitir que el sistema seleccione un grupo razonable por omisión para nuestro nuevo hilo. El hilo así creado es miembro permanente del grupo al cual se una durante su creación -no puede ser cambiado. Si se crea un hilo sin especificar su grupo en el constructor, el sistema de ejecución automáticamente pone el nuevo hilo en el mismo grupo que el hilo que lo crea. Cuando una aplicación de Java arranca, el sistema de ejecución de Java crea un ThreadGroup llamado main. A menos que se especifique lo contrario, todos los nuevos hilos que se creen durante el desarrollo de la aplicación serán miembros del grupo de hilos main.
La clase Thread provee tres constructores que te permiten asignar a un grupo al hilo que estás creando. Finalmente la clase ThreadGroup provee un conjunto de métodos que te permiten obtener información como: qué otros hilos pertenecen al mismo grupo, modificar los hilos por grupo: suspenderlos, activarlos, detenerlos, etc. todo con una sóla invocación a los métodos respectivos.
A manera de ejemplo consídere:
ThreadGroup migrupo = new ThreadGroup(``Mi grupo de hilos'');
Thread myhilo = new Thread(migrupo, ``Un hilo en mi grupo'');
...
elgrupo = myhilo.getThreadGroup();
...
Con esto concluimos el resumen del soporte para hilos de Java y el resumen del capítulo 4 del libro. En la siguiente sección hablaremos acerca de extensiones que se han propuesto para que Java ofrezca protección de recursos compartidos y detección de abrazos mortales, dos de los problemas más comunes que se presentan en programas paralelos. Estos temas no fueron tocados ni en el libro ni en las exposiciones; se presentan aquí, en un esfuerzo por complementar el manejo de hilos, ya que si bien es bueno que el lenguaje ofrezca soporte para ellos de manera nativa; en lo visto en el seminario no se atacó su vital importancia en el desarrollo de programas paralelos y aplicaciones distribuidas, nuestra meta principal en este seminario. Trataremos de resaltar algunos de los detalles más importantes que surgen cuando queremos hacer programas paralelos y de contestar a las preguntas: ¿Es adecuado el soporte de hilos en Java para este tipo de sistemas? ¿puede ser mejorado?

No hay comentarios:

Publicar un comentario