1. Fundamentos. - Recursos sobre informática

Anuncio
Pr ogr amac ión A v anz ada
Programación Multihilo
Hi los
1. Fundamentos.
Los procesadores y los Sistemas Operativos modernos permiten la multitarea, es decir,
la realización simultánea de dos o más actividades. En la realidad, un ordenador con
una sola CPU no puede realizar dos actividades a la vez. Sin embargo los Sistemas
Operativos actuales son capaces de ejecutar varios programas simultáneamente
aunque sólo se disponga de una CPU: reparten el tiempo entre dos (o más)
actividades, o bien utilizan los tiempos muertos de una actividad (por ejemplo,
operaciones de lectura de datos desde el teclado) para trabajar en la otra. En
ordenadores con dos o más procesadores la multitarea es real, ya que cada
procesador puede ejecutar un hilo o thread diferente. La siguiente figura muestra los
esquemas correspondientes a un programa con uno o dos hilos.
Un proceso es un programa ejecutándose de forma independiente y con un espacio
propio de memoria. Un Sistema Operativo multitarea es capaz de ejecutar más de un
proceso simultáneamente. Un hilo es un flujo secuenc ial simple dentro de un
proceso. Un único proceso puede tener varios hilos ejecutándose.
Un sistema multitarea da realmente la impresión de estar haciendo varias cosas a la vez
y eso es una gran ventaja para el usuario. Sin el uso de hilos hay tareas que son
prácticamente imposibles de ejecutar, particularmente las que tienen tiempos de
espera importantes entre etapas.
Los sistemas de un solo hilo utilizan un enfoque llamado bucle de suceso con sondeo.
Cuando un hilo se bloquea deteniendo su ejecución, el programa completo se
detiene.
Existen dos tipos distintos de multitarea:
BASADA EN PROCESOS
•
•
•
•
Permite a la computadora ejecutar más de un proceso concurrentemente.
La unidad de código más pequeña es el proceso.
Los procesos son tareas pesadas que necesitan su propio espacio de
direccionamiento.
La comunicación entre procesos es cara y limitada.
Flo r ent ino Fdez. Rivero la
Dpto . I nfo rm át ica
UN IVE RSIDA D DE VIG O
1
Pr ogr amac ión A v anz ada
Programación Multihilo
BASADA EN HILOS
•
•
•
•
•
Un programa simple puede realizar más de una tarea simultáneamente.
La unidad de código más pequeña es el hilo.
Los hilos (procesos ligeros) son más ligeros que los procesos (procesos
pesados).
Comparten el mismo espacio de direcciones y el mismo proceso pesado.
La comunicación entre hilos es ligera y el cambio contexto de un hilo al
siguiente es menos costoso.
Java hace uso de entornos multitarea basados en procesos, pero la multitarea basada en
hilos está bajo el control de Java.
2. El modelo de hilo de Java.
El intérprete de Java utiliza las prioridades para determinar cómo debe tratar cada hilo
con respecto a los demás. La prioridad de un hilo se utiliza para decidir cuándo se
pasa a ejecutar otro hilo (cambio de contexto).
Nacido
start
Listo
despachar
expiración (asignar un
de cuantum procesador)
notify / notifyAll
wait
en Espera
expira el intervalo de sueño
en
Ejecución
sleep
completar E/S
suspend
dormido
emitir solicitud
de E/S
suspendido
stop
completar
bloqueado
resume
muerto
Ciclo de vida de un hilo
Las reglas que determinan un cambio de contexto son las siguientes:
•
Un hilo puede ceder voluntariamente el control por abandono explícito, al
quedarse dormido o al bloquearse en espera de una operación de E/S pendiente.
Flo r ent ino Fdez. Rivero la
Dpto . I nfo rm át ica
UN IVE RSIDA D DE VIG O
2
Pr ogr amac ión A v anz ada
•
•
Programación Multihilo
=> Se examinan los hilos restantes y se selecciona aquel que tenga mayor
prioridad.
Un hilo que no libera la CPU puede ser desalojado por otro de mayor prioridad.
=> (multitarea por desalojo).
En el caso de hilos de igual prioridad que compiten por la CPU, depende del S.O.
(¡precaución!). En Windows 95 los hilos de igual prioridad se reparten la CPU
(time-slicing) mediante un algoritmo circular (round-robin). En Solaris 2.x los
hilos de igual prioridad deben ceder el control voluntariamente, sino los demás
hilos no se ejecutarán.
SINCRONIZACIÓN
Java implementa una versión de un modelo clásico de sincronización entre procesos,
llamado monitor (definido inicialmente por Hoare, y que puede entenderse como una
pequeña caja negra en la que sólo cabe un hilo).
Los monitores se utilizan para proteger un recurso compartido y evitar que sea
manipulado por más de un hilo simultáneamente.
En Java cada objeto tiene su propio monitor, en el que se entra cuando se llama a uno de
los métodos sincronizados del objeto. Una vez que un hilo está dentro de un método
sincronizado, ningún otro hilo puede llamar a otro método sincronizado del mismo
objeto.
INTERCAMBIO DE MENSAJES
Java proporciona métodos predefinidos que permiten que un hilo entre en un método
sincronizado de un objeto, y espere ahí hasta que otro hilo le notifique explícitamente
que debe salir de él.
LA CLASE THREAD Y LA INTERFAZ RUNNABLE
Para crear un nuevo hilo en Java, el programa debe heredar de la clase Thread o
implementar la interfaz Runnable.
Los métodos básicos para gestionar los hilos se enumeran a continuación:
public final String getName()
Obtiene el nombre de un hilo.
public final int getPriority()
Obtiene la prioridad de un hilo.
public final native boolean isAlive()
Comprueba si un hilo se está ejecutando todavía. Devuelve true si todavía está
ejecutándose o false en caso contrario.
public final void join() throws InterruptedException
Espera hasta que finalice el hilo sobre el que se llama.
Flo r ent ino Fdez. Rivero la
Dpto . I nfo rm át ica
UN IVE RSIDA D DE VIG O
3
Pr ogr amac ión A v anz ada
Programación Multihilo
public final void resume()
Reanuda la ejecucuión de un hilo suspendido
public void run()
Punto de entrada de un hilo.
public static native void sleep(long millis) throws InterruptedException
Suspende un hilo durante un período de tiempo
public native synchronized void start()
Comienza un hilo llamando a su método run().
public final void stop()
Detiene la ejecución de un hilo.
public final void suspend()
Suspende un hilo.
3. El hilo principal. Creación de un hilo.
Cuando se ejecuta un programa Java ya existe un hilo en ejecución, llamado hilo
principal. Este hilo es especial por dos razones:
•
•
Desde él se crearán el resto de hilos del programa.
Debe ser el último hilo que termine su ejecución. Si un hilo principal finaliza
antes que un hijo, Java puede bloquearse (hang).
El método run() es el punto de entrada de un nuevo hilo de ejecución concurrente dentro
de un programa. El hilo termina cuando finalice el método run().
Para que un hilo comience su ejecuc ión se debe llamar a su método start().
La mayoría de los programadores en Java crean hilos heredando de la clase Thread
cuando modifican o mejoran dicha clase. Si lo que se necesita es simplemente la
funcionalidad de un hilo, lo normal es implementar la interfaz Runnable.
4. Creación de múltiples hilos. isAlive(), join(), suspend(), resume().
Los programas en Java pueden generar tantos hilos como necesiten.
Para asegurarse de que un hilo hijo ha terminado se pueden utilizar dos métodos:
isAlive() y join().
Se puede pasar un hilo que está ejecutándose al estado “suspendido” mediante la
llamada al método suspend(), para reanudarlo se llama al método resume(). Esto
Flo r ent ino Fdez. Rivero la
Dpto . I nfo rm át ica
UN IVE RSIDA D DE VIG O
4
Pr ogr amac ión A v anz ada
Programación Multihilo
puede ser útil por ejemplo, en el caso de un hilo que implemente un reloj en pantalla.
Si el usuario no desea el reloj, el hilo puede suspenderse.
5. Prioridades. setPriority(), getPriority().
Los hilos realizan diferentes tareas dentro de nuestros programas, y estas tareas pueden
tener adscritos diferentes niveles de importancia. Para reflejar la importancia de las
tareas que realizan, cada hilo tiene asociada una prioridad utilizada por el sistema en
tiempo de ejecución como ayuda para determinar qué hilo debe ejecutarse en un
instante determinado.
La manera en la que un S.O. implementa la multitarea puede afectar al tiempo de CPU
disponible para cada hilo. Si se desea lograr un comportamiento predecible sobre
distintas plataformas, se deben programar hilos que voluntariamente cedan el control
a la CPU. En concreto, los hilos que comparten la misma prioridad deben ceder el
control de vez en cuando para asegurar que todos los hilos tengan oportunidad de
ejecutarse en un S.O. con multitarea no apropiativa (non-preemptive).
Cuándo ocurre exactamente el desalojo depende de la máquina virtual que tengamos.
No hay garantía, sino sólo la esperanza general de que la preferencia se dará
generalmente a los hilos de mayor prioridad en ejecución. Es conveniente utilizar la
prioridad sólo para afectar a la política de planificación con el objetivo de mejorar la
eficiencia. No es conveniente basar el diseño de un algoritmo en la prioridad de los
hilos.
Para escribir código con múltiples hilos que se ejecute correctamente en diversas
plataformas debemos asumir que un hilo puede ser desalojado en cualquier
momento, por lo que hay que proteger siempre el acceso a los recursos compartidos.
Tampoco se debe realizar suposiciones sobre el orden en que se conceden los
bloqueos a los hilos ni sobre el orden en el que los hilos en espera recibirán las
notificaciones, ya que todos esos aspectos dependen de cada sistema.
Cuando está ejecutándose un hilo de baja prioridad y otro de mayor prioridad se
despierta de su sueño o de la espera de cierta operación de E/S, el hilo de menor
prioridad es desalojado.
Para asignar una prioridad a un hilo se utiliza el método setPriority(). Los posibles
valores están comprendidos entre MIN_PRIORITY (1) y MAX_PRIORITY (10).
La constante NORM_PRIORITY (5) establece la prioridad por defecto. Para
obtener la prioridad de un hilo se utiliza el método getPriority().
6. Grupos de hilos. La clase THREADGROUP
Todo hilo de Java debe formar parte de un grupo de hilos (ThreadGroup). Puede
pertenecer al grupo por defecto o a uno explícitamente creado por el usuario. Los
grupos de hilos proporcionan una forma sencilla de manejar múltiples hilos como un
solo objeto. Así, por ejemplo es posible parar varios hilos con una sola llamada al
Flo r ent ino Fdez. Rivero la
Dpto . I nfo rm át ica
UN IVE RSIDA D DE VIG O
5
Pr ogr amac ión A v anz ada
Programación Multihilo
método correspondiente. Una vez que un hilo ha sido asociado a un grupo de hilos,
no puede cambiar de grupo.
Representación de los grupos de hilos
Cuando se arranca un programa, el sistema crea un ThreadGroup llamado main. Si en
la creación de un nuevo hilo no se especifica a qué grupo pertenece,
automáticamente pasa a pertenecer al threadgroup del hilo desde el que ha sido
creado (conocido como current thread group y current thread, respectivamente). Si
en dicho programa no se crea ningún ThreadGroup adicional, todos los hilos
creados pertenecerán al grupo main (en este grupo se encuentra el método main()).
Para conseguir que un hilo pertenezca a un grupo concreto, hay que indicarlo al crear el
nuevo hilo, según uno de los siguientes constructores:
public Thread (ThreadGroup grupo, Runnable destino)
public Thread (ThreadGroup grupo, String nombre)
public Thread (ThreadGroup grupo, Runnable destino, String nombre)
A su vez, un ThreadGroup debe pertenecer a otro ThreadGroup. Como ocurría en el
caso anterior, si no se especifica ninguno, el nuevo grupo pertenecerá al
ThreadGroup desde el que ha sido creado (por defecto al grupo main). La clase
ThreadGroup tiene dos posibles constructores:
ThreadGroup (ThreadGroup parent, String nombre);
ThreadGroup (String nombre);
Donde el segundo de los cuales toma como padre al grupo de hilos al cual pertenezca el
hilo desde el que se crea (Thread.currentThread()).
En la práctica los grupos de hilos no se suelen utilizar demasiado. Su uso práctico se
limita a efectuar determinadas operaciones de forma más simple que de forma
individual. En cualquier caso, véase el siguiente ejemplo:
ThreadGroup miThreadGroup = new ThreadGroup("Mi Grupo de Threads");
Thread miThread = new Thread(miThreadGroup, ”un thread para mi grupo");
Donde se crea un grupo de hilos (miThreadGroup) y un hilo que pertenece a dicho
grupo (miThread).
Flo r ent ino Fdez. Rivero la
Dpto . I nfo rm át ica
UN IVE RSIDA D DE VIG O
6
Pr ogr amac ión A v anz ada
Programación Multihilo
7. Sincronización. La sentencia synchronized.
Cuando dos o más hilos necesitan acceder de manera simultánea a un recurso
compartido, necesitan asegurarse de que sólo uno de ellos accede al mismo en un
instante dado.
El hecho de que varios hilos llamen al mismo método sobre el mismo objeto a la vez se
denomina “condición de carrera” (race codition).
En C/C++ la sincronización de procesos se realiza mediante llamadas a primitivas del
Sistema Operativo. Java implementa la sincronización a través de elementos del
propio lenguaje (métodos synchronized).
En Java existen dos niveles de bloqueo de un recurso: a nivel de objetos y a nivel de
clases
BLOQUEO A NIVEL DE OBJETOS: MÉTODOS S YNCHRONIZED
Una clase cuyos objetos se deben proteger de interferencias en un entorno con múltiples
hilos declara generalmente sus métodos apropiados como synchronized. Si un hilo
invoca a un método synchronized sobre un objeto, en primer lugar se adquiere el
bloqueo de ese objeto, se ejecuta el cuerpo del método y después se libera el bloqueo.
Otro hilo que invoque un método synchronized sobre ese mismo objeto se bloqueará
hasta que el bloqueo se libere.
La sincronización fuerza a que la ejecución de los dos hilos sea mutuamente exclusiva
en el tiempo. Los accesos no sincronizados no esperan por ningún bloqueo, sino que
proceden independientemente de los bloqueos que pueda haber en el objeto.
La posesión de los bloqueos es por hilo, por lo que al invocar a un método sincronizado
desde el interior de otro método sincronizado sobre el mismo objeto procederá sin
bloqueo. El bloqueo se liberará sólo cuando el método sincronizado más externo
retorne. Este comportamiento de posesión por hilo impide que un hilo se bloquee por
un bloqueo que ya posee, y permite invocaciones recursivas a métodos e
invocaciones de métodos heredados, que pueden estar sincronizados.
Los constructores no necesitan ser synchronized porque se ejecutan sólo cuando se crea
un objeto, y eso sólo puede suceder en un hilo para un objeto dado. De hecho, los
constructores no pueden ser declarados synchronized.
Cuando una clase extendida redefine a un método synchronized, el nuevo método
puede ser synchronized o no. El método de la superclase será synchronized cuando
se invoque. Si el método no sincronizado de la subclase utiliza super para invocar al
método de la superclase, el bloqueo del objeto se adquirirá en ese momento y se
liberará cuando se vuelva del método de la superclase.
BLOQUEO A NIVEL DE CLASE: MÉTODOS ESTÁTICOS SINCRONIZADOS
Flo r ent ino Fdez. Rivero la
Dpto . I nfo rm át ica
UN IVE RSIDA D DE VIG O
7
Pr ogr amac ión A v anz ada
Programación Multihilo
Los métodos estáticos también se pueden declarar synchronized. Todos los objetos
tienen asociado un objeto Class. Los métodos estáticos sincronizados adquieren el
bloqueo del objeto Class de su clase. Dos hilos no pueden ejecutar métodos estáticos
sincronizados de la misma clase al mismo tiempo. Si se comparten datos estáticos
entre hilos, su acceso debe ser protegido utilizando métodos estáticos sincronizados.
La adquisición del bloqueo del objeto Class en un método estático sincronizado no
afecta a los objetos de esa clase. Se puede seguir invocando a métodos sincronizados
de un objeto mientras otro hilo tiene el bloqueo del objeto Class en un método
estático sincronizado. Sólo se bloquean otros métodos estáticos sincronizados.
Para sincronizar el acceso a objetos de una clase que no fue diseñada para acceso
multihilo, se realizan las llamadas a los métodos dentro de un bloque sincronizado:
synchronized(objeto)
{
// sentencias que deben ser sincronizadas;
}
8. Comunicación entre hilos.
Java proporciona un mecanismo de comunicación entre procesos a través de los
métodos wait(), notify(), y notifyAll() definidos en la clase Object (todas las clases
disponen de ellos).
Cualquiera de estos tres métodos sólo puede ser llamado desde dentro de un método
synchronized.
public final void wait() throws InterruptedException
Le indica al hilo en curso que abandone el monitor y se vaya a dormir hasta que otro
hilo entre en el mismo monitor (algún método sinchronized de la misma clase) y
llame a notify(). Existen otras formas para este método donde se permite
especificar un período de tiempo de espera.
public final native void notify()
Despierta al primer hilo que realizó una llamada a wait() sobre el mismo objeto.
public final native void notifyAll()
Despierta todos los hilos que realizaron una llamada a wait() sobre el mismo objeto.
El hilo con mayor prioridad de entre los que han sido despertados es el primero en
ejecutarse.
Un buen ejemplo es el problema del productor/consumidor modificado de forma que el
productor tiene que esperar a que el consumidor haya terminado para empezar a
producir más datos.
9. Bloqueos. deadlock.
Flo r ent ino Fdez. Rivero la
Dpto . I nfo rm át ica
UN IVE RSIDA D DE VIG O
8
Pr ogr amac ión A v anz ada
Programación Multihilo
Este tipo de error está relacionado con la multitarea y es necesario evitarlo. Se produce
cuando dos hilos tienen una dependencia circular en un par de objetos sincronizados.
Siempre que tenemos 2 hilos y 2 objetos con bloqueo, puede producirse un deadlock. Se
trata de una situación en la que cada uno de los hilos tiene el bloqueo de uno de los
objetos y está esperando por el bloqueo del otro objeto. Si un objeto X tiene un
método synchronized que invoca a un método synchronized del objeto Y, y éste a
su vez tiene un método synchronized que invoca a un método synchronized del
objeto X, puede suceder que dos hilos estén esperando a que el otro finalice para
obtener el bloqueo, y ninguno de los dos podrá ejecutarse. Esta situación se
denomina también abrazo mortal.
El programador es el responsable de evitar que se produzcan deadlocks. El sistema en
tiempo de ejecución no los detecta, ni los evita.
Los deadlocks son un tipo de error difícil de resolver por dos razones:
•
•
Ocurre en raras ocasiones, cuando los dos hilos intentan entrar a la vez.
En el bloqueo pueden estar involucrados más de dos hilos y dos objetos
sincronizados.
Flo r ent ino Fdez. Rivero la
Dpto . I nfo rm át ica
UN IVE RSIDA D DE VIG O
9
Descargar