G.Bordel >Docencia >TAP Técnicas Actuales de Programación (curso 2010-2011)
desprotegido Intro. desprotegido Temario desprotegido Calendario desprotegido RPF desprotegido Recursos protegido Práctica protegido Gest. Alum.
tema_anterior Tema 10: Hilos tema_siguiente
  1. Introducción.
  2. Ciclo de vida de un hilo.
  3. Distribución de la CPU. Prioridades. El problema de la "inanición".
  4. Mecanismos de sincronización de hilos. El problema del interbloqueo.
  5. Agrupamientos de hilos.
  6. Estudio de un ejemplo de programación con hilos.[ejercicios]

10.1- Introducción

En general, nuestros computadores actuales son máquinas secuenciales que ejecutan instrucciones una tras otra. En los inicios de la computación con máquinas esto no era un concepto destacable; álgo habia de ello ya desde la máquina de diferencias de Charles Babbage, o incluso antes si pensamos en máquinas más limitadas, pero quizás lo natural para el pensamiento humano es plantear mecanismos complejos para tareas complejas de computación que trabajan simultaneamente en la resolución de partes del problema global. Muchas de las grandes máquinas construidas en los principios de la aplicación de la electrónica a la computación no eran secuenciales (p.ej. la ENIAC, una máquina de 130 toneladas y 18000 tubos de vacio -año aprox. 1943-), y fue sólo a partir del momento en que Von Newmann recogió una serie de ideas para el diseño de una máquina que la harían eficaz en cuanto que aportaban una estructura manejable (una arquitectura) que se asumió la secuencialidad como característica necesaria.

Conforme la electrónica ha avanzado y alcanzado grados de integración de componentes varios ordenes de magnitud superiores a los de los primeros procesadores y, sobre todo, gracias a los avances en los sistemas de ayuda al diseño de circuitos nos está permitido hoy en día plantear el paralelismo en el hardware bajo varios puntos de vista pero en todo caso es siempre un paralelismo entre procesos secuenciales bien definidos (ya sea en el "multithreading", los sistemas multiprocesador o en la computación distribuida).

Pero dejando a parte estas capacidades hardware, también sobre un sistema puramente secuencial podemos disponer de múltiples procesos que se ejecutan en cierto modo en paralelo. Fué en 1961 cuando Fernando Corbató (MIT) introdujo el concepto de "tiempo compartido", según el cual un procesador dedica pequeños intervalos de tiempo a diferentes tareas de modo que desde el nuestro punto de vista (nuesta escala de percepción del tiempo) se aprecia una ejecución paralela. Esto no es meramente una técnica de "reparto" de la capacidad de computo sino que permite además una colaboración entre las distintas tareas y por tanto una estructuración más flexible de las soluciones software.

La Máquina Virtual Java es una máquina que implementa esta capacidad de dedicar intervalos de tiempo a diferentes secuencias de ejecución y la utilización de esta característica es muy sencilla (no debe confundirse esta sencillez de uso con el hecho de que una solución con ejecución parlela sea más o menos sencilla, ya que en general la computación en paralelo presenta complicaciones nada triviales que requieren conocimientos específicos).

La siguiente figura representa una ejecución en el tiempo de un programa generado en Java. Inicialmente se ponen en marcha dos "hilos" de ejecución (dos secuencias de programa") entendiendo que realmente cada uno es ejecutado a intervalos mientras el otro está en espera. Estos dos hilos se corresponden, uno con el programa que comienza en el método main, y otro con un programa que aporta de modo automático la JVM: el recolector de basuras, que examina el uso de memoria en busca de elementos fuera de uso para eliminarlos y reintegrar el espacio que ocupaban al conjunto del espacio libre disponible.

Desde el hilo principal podemos poner en marcha nuevos hilos de ejecución y estos a su vez podrán activar otros. En todo caso, estos hilos de ejecución comparten un mismo espacio de memoria (puede accederse a elementos de otros hilos siempre que las condiciones de accesibilidad -los atributos- lo permitan). Este uso de memoria común permite una interacción entre hilos muy simple (esto es una ventaja que no ha de usarse inconscientemente ya que una ligadura tan fuerte entre hilos puede llegar a comprometer la modularidad). Esta posiblidad de interacción permite la colaboración en la resolución de tareas complejas de un modo más estructurado que sin hilos. Evidentemente, el hecho de disponer de acceso al mismo de memoria no imposibilita que la comunicación entre procesos se haga de modos más "debiles" si su arquitectura lo requiere (pipes, sockets u otros).


Un proceso Java con varios hilos.

La Máquina Virtual Java es a su vez un proceso ejecutado por un sistema operativo y por lo tanto paralelo a otros procesos. En este caso no hay compartición de la memoria y la posible colaboración entre procesos pasa por la utilización de otros mecanísmos de entre los que destaca el paso de mensajes a través de las utilidades que el sistema operativo ofrece con dicho fin. La siguiente figura muestra la ejecución de cuatro procesos en un sistema operativo, dos de los cuales son "monohilo" y otros dos "multihilo" (pueden ser JVMs).


Procesos en un Sistema Operativo.

Como se ha mencionado, la puesta en marcha de nuevos hilos de ejecución en java es algo muy sencillo. La idea es tan simple como que podemos escribir clases tales que los objetos instanciados funcionen separadamente del hilo que los instancia (pueden dar lugar a un nuevo hilo de ejecución). Hay dos modos de escribir tales clases; comenzaremos por la más básica.

La capacidad de funcionar en un hilo nuevo es una capacidad que heredan todas las clases descendientes de Thread. Esta clase dispone de un método public void run() que debemos reescribir en las subclases con el código que arranca el nuevo hilo (es el equivalente al main del programa principal pero sin sus parámetros ya que en este caso pueden aportarse con el constructor).

El hecho de instanciar un objeto de una de estas clases no implica que se ponga en marcha el nuevo hilo, sino que podemos retrasar dicho momento si es preciso (p.ej. mientras se instancian otros objetos semejantes para arrancarlos conjuntamente). Esto es posible porque el arranque del hilo se requiere mediante una llamada al método start(). En caso de que queramos que los objetos instanciados arranquen el hilo de inmediato podemos situar la llamada a start() en el cuerpo del constructor.


.

El mecanismo visto tiene un inconveniente en el caso en que pretendamos escribir una clase que herede de otra ya que, al no disponer de herencia múltiple no podremos hacer que sea también descendiente de Thread. En tales casos hemos de declarar que nuestra nueva clase implemente el interfaz Runnable. Esto nos exige escribir el método public void run() y nos permite aportar objetos de dicha clase como parámetros al constructor de Thread al instanciar un nuevo hilo donde correrá el objeto Runnable que le aportamos. (Obsérvese que tenemos dos objetos: nuestro objeto "runnable" y un objeto "hilo" al que le damos el primero. Lo que ha de ponerse en marcha es el hilo -mediante su "start"- y este se encarga de que el codigo a ejecutar sea el del objeto "runnable")


.

Siguiente punto: 10.2- Ciclo de vida de un hilo


Plataforma de soporte a curso y contenidos (c) German Bordel 2005.