Professional Documents
Culture Documents
PRÁCTICA
PROGRAMACIÓN ORIENTADA A OBJETOS (ARCHIVOS E HILOS)
Hilos
Cuando se genera un proceso, se crea un hilo primario, que está asociado a un bloque de
instrucciones, un conjunto de registros y una pila. Este hilo, puede crear más hilos si es
necesario; a estos hilos se les denomina “procesos ligeros” ya que comparten ciertos
recursos con el proceso primario que los creó.
PRÁCTICA
PROGRAMACIÓN ORIENTADA A OBJETOS (ARCHIVOS E HILOS)
Pero, ¿para qué se necesitan varios hilos? Cuando se hace una aplicación robusta en el que
tenga que hacer varias acciones al mismo tiempo, en lugar de crear un proceso completo, es
más conveniente crear un hilo que pueda llevar a cabo las acciones correspondientes de una
forma rápida y eficaz. Por ejemplo, si se tiene un sistema que está en red, para que
múltiples usuarios tengan acceso, si se generara un proceso por cada usuario, saldría
bastante caro, hablando de recursos máquina, pero si se genera un hilo, para que haga una
consulta, o una actualización de un dato, las acciones se realizan rápidamente.
Hay varios estados por los que pasa un hilo, y son:
Preparado.- Cuando se genera un hilo nuevo y está listo para ejecutarse, o cuando
ha sido desbloqueado.
En ejecución.- Cuando está haciendo uso de los recurso de la CPU.
Bloqueado.- Cuando está en espera de que se elimine el bloqueo.
o Dormido.- Cuando se bloquea por un cierto tiempo, después del cual
despierta, y pasa a preparado.
o En espera.- Cuando está esperando a que ocurra algo, recibe un mensaje y
pasa a preparado.
Muerto.- Cuando ha terminado de ejecutarse.
En Java, cuando se trabaja con hilos, se debe indicar que la clase con la que se está
trabajando es una subclase de la clase Thread, es decir, que se heredarán todas las
propiedades que contiene la clase Thread.
Ejemplo 3
Elabore un programa que genere 6 hilos.
public class GeneraHilo extends Thread{
//Rutina que se ejecuta cuando se le da start() a un hilo
public void run(){
System.out.println("Esta es una instancia de nuestra clase: "
+this.getName());
}
public static void main(String args[]){
//Se generan 6 hilos
GeneraHilo gh1 = new GeneraHilo();
GeneraHilo gh2 = new GeneraHilo();
GeneraHilo gh3 = new GeneraHilo();
GeneraHilo gh4 = new GeneraHilo();
GeneraHilo gh5 = new GeneraHilo();
GeneraHilo gh6 = new GeneraHilo();
//Se les asigna nombre
gh1.setName("Hilo1");
gh2.setName("Hilo2");
gh3.setName("Hilo3");
gh4.setName("Hilo4");
gh5.setName("Hilo5");
gh6.setName("Hilo6");
EJERCICIOS PROPUESTOS
1. El programa del Ejemplo 1, tiene una falla cuando no se le pasan parámetros,
arréglelo utilizando un catch. Además, cuando se le da el nombre de un archivo no
existente aparece la leyenda
(El sistema no puede hallar el archivo especificado)
Modifique el programa de tal forma que se indique:
ERROR!!!
Archivo no válido, favor de verificar el nombre del archivo.
TIP: Utilice una excepción para la modificación de archivo no válido.
2. Modifique el programa del Ejemplo 2 de tal forma que ahora se escriba en archivo
todo lo que se escriba mientras está en ejecución el programa.
3. Modifique el programa anterior, para que también se pueda agregar información al
final del archivo que se indique.
4. Elabore un programa que lea información de un archivo, y lo escriba en otro.
5. Cuando se ejecuta varias veces seguidas el programa del Ejemplo 3, ¿qué pasa?
Ahora en el método run(), agregue la instrucción
sleep((long)Math.random()*2500), antes de la impresión del nombre del
hilo que se está ejecutando. Haga las modificaciones necesarias para que compile y
ejecute correctamente el programa. Si se ejecuta varias veces seguidas el programa,
¿Ahora, qué sucede?
Concepto de Hilos de Ejecución
Para solucionar este problema, los programadores han creado lo que se llaman "hilos de
ejecución" (Threads). Su razonamiento es que cada proceso es una colección de uno o más
"hilos". Todos los programas tienen al menos un hilo, que es en el caso de los programas de
Delphi (y de casi todos los lenguajes de Windows) el mismo hilo que procesa todos los
mensajes. Pero cualquier hilo puede crear otro "hilo" con un pedazo de código, y este hilo
podrá ejecutar al mismo tiempo que el programa, sin necesidad de interrumpirlo.
Esto puede ser bastante complejo; ya que aunque Windows puede protegerlo de accesar
memoria que pertenece a otros procesos, no lo va a ayudar si varios hilos tratan de accesar
la misma memoria. Así que usted tiene que tener cuidado, cuando accesa las variables de
un hilo, de que el hilo no la esté utilizando, porque si no lo hace, los resultados pueden ser
impredecibles.
Así que usted puede hacer que varios hilos de ejecución ejecuten simultáneamente en su
programa, pero no puede hacer que los hilos accesen memoria de otros hilos al mismo
tiempo... ¿Entonces, cómo le hacemos para comunicarnos?
El caso más común donde desearíamos comunicarnos con el hilo de ejecución principal
sería para escribir algo en la pantalla (tal vez aumentar el valor de un ProgressBar o escribir
en una etiqueta el estado actual de nuestro hilo). Para comunicarnos entre diversos hilos,
debemos detener todos los hilos en favor del hilo principal, y decirle a este hilo que
actualice la pantalla por nosotros. Este procedimiento se llama "sincronización".
A continuación presento una pieza simplificada de código con las porciones indispensables
para que un hilo de ejecución funcione:
type
TBoxParser = class(TThread)
private
protected
procedure Execute; override;
procedure MostrarProgreso; override;
public
end;
implementation
procedure TBoxParser.Execute;
var
i : Integer;
begin
end;
procedure TBoxParser.MostrarProgreso;
begin
end;
Prioridad y Señales
Los hilos de ejecución pueden tener varios niveles prioridad. El hilo puede cambiar su
propia prioridad, pero debe usted tener en cuenta de que otros hilos en su programa (o el
sistema operativo mismo) puede cambiar su prioridad, así que procure no depender en la
prioridad.
Ya hemos visto que otros hilos de ejecución pueden pedir que terminemos el programa, y
eso lo debemos manejar nosotros mismos. Esta es una de las señales que el programa nos
puede enviar, pero también puede suspendernos y continuarnos (Suspend/Resume).
Siempre tenga esto en cuenta y programe defensivamente. Por ejemplo, si usted utiliza un
recurso en un loop, asegúrese de que el recurso exista dentro del loop. Esa clase de cosas.
Uso de Recursos
Los hilos de ejecución que usted implemente deben estar programados de tal manera que
"jueguen limpio" con los recursos. Esto quiere decir verificar que el recurso no esté en uso
por otra tarea.
Uno de los recursos que debemos procurar no accesar en varios hilos al mismo tiempo son
las conexiones a bases de datos. El BDE permite multitarea pero no sobre la misma
conexión. Esto quiere que usted deberá:
En Delphi es más sencillo crear los hilos y asignarles un nuevo TDatabase y TSession a
cada uno.
En el siguiente fragmento de código, estoy creando algunos queries, y al hacer esto estoy
pidiendo la sesión y base de datos especificadas en el miembro "Database", que es un
TDatabase que yo inventé:
procedure TBoxParser.Execute;
var
i : Integer;
begin
qGente := TQuery.Create(Database.Owner);
qEncuestas := TQuery.Create(Database.Owner);
qGente.SessionName := Database.SessionName;
qGente.DatabaseName := Database.DatabaseName;
qEncuestas.SessionName := Database.SessionName;
qEncuestas.DatabaseName := Database.DatabaseName;
qGente.SQL.Add('SELECT * FROM People WHERE EMail = :Email');
qGente.RequestLive := True;
qGente.Params[0].DataType := ftString;
qEncuestas.SQL.Add('SELECT * FROM DelphiEncuestas WHERE EMail =
:Email');
qEncuestas.Params[0].DataType := ftString;
qEncuestas.RequestLive := True;
El código que crea este hilo esta creando la base de datos (cuya variable es DBThread) por
mí. Me conecta (para que el diálogo de Login/Password se ejecute desde el hilo principal),
y una vez que tenga conectada la base, asigna la variable "Database" del hilo a la
TDatabase a la que me conectó. De esta manera puedo estar seguro de que mi base de datos
está lista para acceso. Es responsabilidad del hilo principal no utilizar la base hasta que el
TThread Termine.
FParseThread := TBoxParser.Create(True);
dbThread.Connected := True;
TBoxParser(FParseThread).Database := dbThread;
FParseThread.Resume;
Cuando diseñamos programas con varios hilos de ejecución que hagan tareas repetitivas,
usted debe estar consciente de el manejo los hilos de ejecución en los programas para
asegurarse de que su programa no se trabe por uso simultáneo de recursos o demasiados
hilos a la vez. Estas consideraciones nos hacen tener que decidir entre dos modelos de
diseño:
Bajo este modelo, cada cliente que usted genera crea un nuevo hilo. Cuando el cliente se
desconecta (o el hilo termina su operación), el hilo se borra. Esto es fácil de programar. Por
ejemplo, el evento OnClick de un boton puede crear un TThread y ejecutarlo. Pero el
problema es que es fácil trabar el sistema si no tenemos cuidado (el usuario presionando el
boton mil veces a la vez). El manual del API de Windows nos recomienda no crear más de
16 hilos de ejecución secundarios para evitar inestabilidad.
Aunque es fácil de escribir, este modelo sólo funciona para unos cuantos hilos, y entre más
hilos sean necesarios, más importante es encontrar otra manera.
En un Thread Pool, usted tiene un objeto que mantiene una lista de punteros a hilos de
ejecución que pueden estar "libres" u "ocupados". Esta colección corre desde el contexto
del hilo principal y el hilo principal utiliza la lista para pedir un nuevo hilo. La lista busca
entre todos los hilos uno que no esté ocupado. Si encuentra uno, lo proporciona. Si no, crea
uno nuevo, lo añade a la lista y lo proporciona, a menos que la lista tenga el Máximo de
hilos permitidos, que usted determina (en cuyo caso se crea el error "Servidor demasiado
ocupado").
Una vez que el hilo ha terminado la lista se actualiza marcando el hilo como "libre".
Este procedimiento es excelente para los usuarios que necesitan compartir recursos como
conexiones de red o conexiones a bases de datos. Estos sistemas son más difíciles de
escribir porque debemos crear la colección y mantener la lista, además de asegurarnos de
que el código existente nunca llame a los hilos por sí mismo (sino siempre use la lista).
Pero nos proporciona dos ventajas:
Primero, nos permite reutilizar hilos que no estén siendo usados, haciendo que nuestro
programa pueda dar servicio a más clientes y a la vez reduzca los requerimientos sobre el
sistema. Segundo, la complejidad se queda en el módulo de la colección, y mientras usted
sea disciplinado en su programación de objetos, cualquier error en su modelo de hilos de
ejecución probablemente será un error en la manera de implementar esta lista.
3. Programación concurrente multihilo
Introducción:
Un hilo es básicamente una tarea que puede ser ejecutada en paralelo con otra
tarea. Los hilos de ejecución que comparten los mismos recursos, sumados a
estos recursos, son en conjunto conocidos como un proceso. El hecho de que los
hilos de ejecución de un mismo proceso compartan los recursos hace que
cualquiera de estos hilos pueda modificar éstos. Cuando un hilo modifica un dato
en la memoria, los otros hilos acceden a ese dato modificado inmediatamente.
Un hilo es básicamente una tarea que puede ser ejecutada en paralelo con otra
tarea.
Los hilos de ejecución que comparten los mismos recursos, sumados a estos
recursos, son en conjunto conocidos como un proceso. El hecho de que los hilos
de ejecución de un mismo proceso compartan los recursos hace que cualquiera de
estos hilos pueda modificar éstos. Cuando un hilo modifica un dato en la memoria,
los otros hilos acceden a ese dato modificado inmediatamente.
Los hilos a menudo son conocidos o llamados procesos ligeros. Un hilo, en efecto,
es muy similar a un proceso pero con la diferencia de que un hilo siempre corre
dentro del contexto de otro programa. Por el contrario, los procesos mantienen su
propio espacio de direcciones y entorno de operaciones. Los hilos dependen de un
programa padre en lo que se refiere a recursos de ejecución. La siguiente figura
muestra le relación entre hilos y procesos.
}
Aquí, cuando se llama a main(), la aplicación imprime el mensaje y termina. Esto
ocurre dentro de un único thread.
La clase Thread
Es la clase que encapsula todo el control necesario sobre los hilos de ejecución
(threads). Hay que distinguir claramente un objeto Thread de un hilo de ejecución
o thread. Esta distinción resulta complicada, aunque se puede simplificar si se
considera al objeto Thread como el panel de control de un hilo de ejecución
(thread). La clase Thread es la única forma de controlar el comportamiento de los
hilos y para ello se sirve de los métodos que se exponen en las secciones
siguientes.
Métodos de Clase
Estos son los métodos estáticos que deben llamarse de manera directa en la clase
Thread.
currentThread()
Este método devuelve el objeto thread que representa al hilo de ejecución que se
está ejecutando actualmente.
yield()
Este método hace que el intérprete cambie de contexto entre el hilo actual y el
siguiente hilo ejecutable disponible. Es una manera de asegurar que nos hilos de
menor prioridad no sufran inanición.
sleep( long )
Métodos de Instancia
Aquí no están recogidos todos los métodos de la clase Thread, sino solamente los
más interesantes, porque los demás corresponden a áreas en donde el estándar
de Java no está completo, y puede que se queden obsoletos en la próxima versión
del JDK, por ello, si se desea completar la información que aquí se expone se ha
de recurrir a la documentación del interfaz de programación de aplicación (API) del
JDK.
start()
Este método indica al intérprete de Java que cree un contexto del hilo del sistema
y comience a ejecutarlo. A continuación, el método run() de este hilo será
invocado en el nuevo contexto del hilo. Hay que tener precaución de no llamar al
método start() más de una vez sobre un hilo determinado.
run()
stop()
suspend()
resume()
setPriority( int )
El método setPriority() asigna al hilo la prioridad indicada por el valor pasado como
parámetro. Hay bastantes constantes predefinidas para la prioridad, definidas en
la clase Thread, tales como MIN_PRIORITY, NORM_PRIORITY y
MAX_PRIORITY, que toman los valores 1, 5 y 10, respectivamente. Como guía
aproximada de utilización, se puede establecer que la mayor parte de los procesos
a nivel de usuario deberían tomar una prioridad en torno a NORM_PRIORITY. Las
tareas en segundo plano, como una entrada/salida a red o el nuevo dibujo de la
pantalla, deberían tener una prioridad cercana a MIN_PRIORITY. Con las tareas a
las que se fije la máxima prioridad, en torno a MAX_PRIORITY, hay que ser
especialmente cuidadosos, porque si no se hacen llamadas a sleep() o yield(), se
puede provocar que el intérprete Java quede totalmente fuera de control.
getPriority()
Este método devuelve la prioridad del hilo de ejecución en curso, que es un valor
comprendido entre uno y diez.
setName( String )
Este método permite identificar al hilo con un nombre menmónico. De esta manera
se facilita la depuración de programas multihilo. El nombre mnemónico aparecerá
en todas las líneas de trazado que se muestran cada vez que el intérprete Java
imprime excepciones no capturadas.
getName()
Este método devuelve el valor actual, de tipo cadena, asignado como nombre al
hilo en ejecución mediante setName().
Al igual que los procesos, los hilos poseen un estado de ejecución y pueden
sincronizarse entre ellos para evitar problemas de compartimiento de recursos.
Generalmente, cada hilo tiene una tarea especifica y determinada, como forma de
aumentar la eficiencia del uso del procesador.
Estados de un hilo
Los principales estados de los hilos son: Ejecución, Listo y Bloqueado. No tiene
sentido asociar estados de suspensión de hilos ya que es un concepto de proceso.
En todo caso, si un proceso está expulsado de la memoria principal (ram), todos
sus hilos deberán estarlo ya que todos comparten el espacio de direcciones del
proceso.
Cambio de estados
Sincronización de hilos
Todos los hilos comparten el mismo espacio de direcciones y otros recursos como
pueden ser archivos abiertos. Cualquier modificación de un recurso desde un hilo
afecta al entorno del resto de los hilos del mismo proceso.Por lo tanto, es
necesario sincronizar la actividad de los distintos hilos para que no interfieran unos
con otros o corrompan estructuras de datos.
Una ventaja de la programación multihilo es que los programas operan con mayor
velocidad en sistemas de computadores con múltiples CPUs (sistemas
multiprocesador o a través de grupo de máquinas) ya que los hilos del programa
se prestan verdaderamente para la ejecución concurrente. En tal caso el
programador necesita ser cuidadoso para evitar condiciones de carrera (problema
que sucede cuando diferentes hilos o procesos alteran datos que otros también
están usando), y otros comportamientos no intuitivos. Los hilos generalmente
requieren reunirse para procesar los datos en el orden correcto. Es posible que los
hilos requieran de operaciones atómicas para impedir que los datos comunes sean
cambiados o leídos mientras estén siendo modificados, para lo que usualmente se
utilizan los semáforos. El descuido de esto puede generar interbloqueo.
Formas de multihilos
Crear un Hilo
Mientras que los programas de flujo único pueden realizar su tarea ejecutando las
subtareas secuencialmente, un programa multihilo permite que cada thread
comience y termine tan pronto como sea posible. Este comportamiento presenta
una mejor respuesta a la entrada en tiempo real.
// y el retardo
nombre = s;
retardo = d;
try {
sleep( retardo );
Test Th t1,t2,t3;
t1.start();
t2.start();
t3. start();
}
3.3 Creación y Control de Hilos
En cuanto al proceso de creación de hilos, son dos los mecanismos que nos
permiten llevarlo a cabo en Java: implementando la interfaz Runnable, o
extendiendo la clase Thread, esto es, creando una subclase de esta clase.
Así, se utilizan para el diseño de requisitos comunes a todas las clases que se
tiene previsto implementar. La interfaz define el trabajo, la funcionalidad que debe
cubrirse, mientras que la clase o clases que implementan la interfaz realizan dicho
trabajo (cumplen esa funcionalidad).
Todas las clases o grupos de clases que implementen una cierta interfaz deberán
seguir las mismas reglas de funcionamiento.
Por ejemplo,
...
}
Thread t;
}
En este caso necesitamos crear una instancia de Thread antes de que el sistema
pueda ejecutar el proceso como un hilo. Además, el método abstracto run que
está definido en la interfaz Runnable tiene que implementarse en la nueva clase
creada.
Por otro lado, es un error pensar que la interfaz Runnable está realizando alguna
tarea mientras un hilo de alguna clase que la implemente se está ejecutando. Es
una interfaz, y como tal, sólo contiene funciones abstractas (concretamente una
única, run), proporcionando tan solo una idea de diseño, de infraestructura, de la
clase Thread, pero ninguna funcionalidad. Su declaración en Java tiene el
siguiente aspecto:
package Java.lang;
…
public void run() {
tarea.run() ;
…}
Se deduce, por tanto, que la propia clase Thread de Java también implementa la
interfaz Runnable. Observamos que en el método run de Thread se comprueba si
la clase con que se está trabajando en ese momento (tarea), que es la clase que
se pretende ejecutar como hilo, es o no nula. En caso de no ser nula, se invoca al
método run propio de dicha clase.
Control de un hilo
Arranque de un hilo
La línea de código:
Runnable) crea un nuevo hilo. Los dos argumentos pasados, sin mayor relevancia,
satisfarán
Al tener control directo sobre los hilos, tenemos que arrancarlos explícitamente.
Como ya se comentó anteriormente, es la función miembro start la que nos
permite hacerlo. En nuestro
ejemplo sería:
t1.start();
Manipulación de un hilo
Si todo fue bien en la creación del objeto Test Th (t1), éste debería contener un
hilo, una traza de ejecución válida, que controlaremos en el método run del objeto.
Suspensión de un Hilo
Parada de un Hilo
t1.stop();
Señalar que esta llamada no destruye el hilo, sino que detiene su ejecución, y ésta
no puede reanudarse con el método start. Cuando se desasignen las variables
que se usan en el hilo, el objeto hilo (creado con new) quedará marcado para
eliminarlo y el garbage collector (recolector de basura de Java) se encargará de
liberar la memoria que utilizaba.
Por último, un método de control de hilos que nos permite comprobar si una
instancia está viva (el hilo se ha arrancado y aún no se ha detenido) o no (bien no
se arrancó; bien ya finalizó).
t1.isAlive();
Devolverá true en caso de que el hilo t1 esté vivo, es decir, ya se haya llamado a
su método run y no haya sido parado con un stop ni haya terminado el método run
en su ejecución. En otro caso, lógicamente, devolverá false.
pthread_attr_t
FIFO
{
Round-robin
Lafuncion pthread_attr_init
Antes de poder usar cualquier función de manejo de hilos, hay que preparar el
sistema para ejecutar estas operaciones, es decir, es necesario inicializar el
sistema de hilos, nada más sencillo.
Es importante no llamar a esta función más de una vez pues, si esto sucediese, la
ejecución de nuestro programa sería abortada. Para evitar esto la forma más
sencilla es hacer:
if (!g_thread_supported ())
g_thread_init (NULL);
Después, el sistema estará listo para realizar cualquier operación que requiera el
uso de hilos. Para empezar, se creará un hilo:
G Thread? *hilo:
hilo = g_thread_create (funcion_hilo, NULL, FALSE, NULL);
Si todo fue bien en la creación del objeto Test Th? (t1), éste debería contener un
hilo, una traza
conocemos. Digamos que es la rutina main a nivel de hilo. Todo lo que queremos
que haga el hilo debe estar dentro del método run. Cuando finalice run, finalizará
también el hilo que lo
ejecutaba.
El último elemento de control que se necesita sobre los hilos de ejecución es el
método stop(). Se utiliza para terminar la ejecución de un hilo:
t1.stop();
Este método devolverá true en caso de que el hilo t1 esté vivo, es decir, ya se
haya llamado a su método run() y no haya sido parado con un stop() ni haya
terminado el método run() en su ejecución.
Sincronización
3.5 SEMAFOROS
Tipos de semáforos
Los semáforos operan con enteros no negativos. En función del rango de valores
posibles, hay dos tipos de semáforos:
BARRERA
En cada iteración del bucle los hilos ejecutarán A sin realizar ninguna
sincronización; a continuación todos ellos esperan a que el resto llegue a la
BARRERA. Una vez que todos han llegado a la barrera (todos han ejecutado A),
continúan ejecutando B y el bucle se repite.
Salidas. Cada hilo deberá informar del número de iteración junto con el punto del
bucle que acaba de ejecutar.
Ejemplos:
–procesamiento de imágenes
Conclusión:
3. http://www.lawebdelprogramador.com
BIBLIOGRAFIA
http://www.hackerdude.com/courses/delphi/Cap011.1.html