You are on page 1of 9

Características de la programación Win32

Hilos de ejecución (Threads)

Los hilos (threads) son una de las características mas importantes y


prácticas en Windows NT/95 y la API Win32 . Los hilos te permiten dividir un
programa simple en múltiples instancias de ejecución paralela. Aquí aprenderemos
los conceptos básicos para la creación de hilos en cualquier programa Win32.

Las posibilidades
Existen diversos casos en los que se puede amplear la programación
mediante hilos. Algunas ideas:
 Si creamos un programa cin interfaz MDI (Multiple Document Interface), es
recomendable asignar separadamente cada ventana en un hilo propio
 Si un programa necesita un largo tiempo para refrescar una imagen por la
complejidad del tipo de gráfico (como puede ocurrir en diseños CAD/CAM),
es conveniente crear en un hilo separado el manejador del proceso que
realiza el redibujado. Mientras, el interfaz de usuario estará activo y el hilo
de redibujado podrá trabajar en segundo plano.
 En programas de simulación, por ejemplo los referentes a simulación de
actividad organica en un entorno determinado,el diseño de estos programas
es, a menudo, más simple si cada entidad está en un hilo propio, puesto
que así son más independientes y pueden presentar respuestas
individuales.
 Si una parte del programa debe responder con una mayor prioridad para
responder más rápidamente a los eventos, el problema se puede resolver
más fácilmente utilizando la prioridad de los hilos. De esta manera se
reciben los ciclos de CPU necesarios para acabar la tarea y posteriormente
pasar a un modo sleep hasta que no llegue el próximo evento a tratar.
 En una máquina con múltiples procesadores podemos aprovechar la
potencia de estas CPU si el programa está dividido en hilos, porque cada
cpu podrá ejecutar un hilo distinto en el mismo instante, mientras que si
sólo tenemos un hilo, el programa se ejecutará normalmente en uno de los
procesadores, quedando el resto desocupados y, por tanto,
desaprovechados. (Recordemos que NT puede ejecutar procesos en varias
cpu’s).

 Cualquier tarea que deba ejecutarse en segundo plano mientras que el


usuario deba continuar trabajando, es candidata a programarse en un hilo
independiente. Por ejemplo cualquier operacion de recalcular valores,
formateos de pagina, lecturas y escrituras en ficheros,etc.

Introducción

Si trabaja en entornos como NT, UNIX, VMS, o mainframe, el concepto de


multi-proceso le será familiar, pero Multi-Hilo (Multi-threading ) en ocasiones será
nuevo para usted. Si trabaja usted desde MS-DOS, ambos conceptos serán
nuevos. Comencemos explicando que significa en los actuales sistemas
operativos los conceptos de multi-processing y multi-threading .
MS-DOS es un sistema de proceso simple, sólo podemos ejecutar un
programa en un instante determinado. La ejecución de otro proceso requiere la
descarga del programa anterior. Los programas tipo TSRs (programas residentes)
pueden dar , en ciertas situaciones impresion de multi-processing, pero en
realidad esto es una ilusion.
Microsoft Windows 3.1 es un entorno multitarea cooperativo, pensando en
que podemos abrir varios programas, pero sólo existirá uno en ejecución. Por
ejemplo podemos ejecutar un procesador de textos en una ventana, una hoja de
calculo en otra, etc. La multitarea cooperativa es usada porque permite a los
programas tomar el control en el momento apropiado. Este método presenta
algunos inconvenientes, en grandes tiempos de acceso a disco o en procesos
individualizados tienden a apropiarse del sistema, y un cuelgue de uno de los
procesos bloquearía a todos los demás.

UNIX es un sistema (preemptive Derecho preferente) . Asigna un tiempo


determinado de CPU a los distintos procesos (Time-SliceTiempo asignado por el
procesador) que puede estimarse en unos 20 ms. Pasado ese tiempo la cpu
conmuta de proceso. Si uno de ellos se cuelga no afecta al resto. Windows NT y
Windows 95 son sistemas preemptively multi-tasking, multi-threaded operating
system.
La programación Multi-threading va un paso más alla. Un programa
individual, por defecto sólo contiene un hilo, pero puede dividirse en varios hilos
independientes, por ejemplo se puede estar enviando un trabajo a imprimir
mientras se solicitan datos a un usuario. este simple cambio reduce
significativamente el tiempo de espera, que tembien se produciría en tareas tales
como accesos de Lec/Escr a disco, recalculos, etc. Multi-threading tambien
permite la ventaja del uso de sistemas operativos con varias cpu’s como NT
Todos los hilos (hijos) de un proceso comparten es espacio de direcciones
de variables del proceso padre, aunque cada hilo posee su propia pila.
Cuando se crea un hilo este tiene acceso a todo el espacio de direcciones
globales del proceso padre. Todos los problemas derivados del uso de variables
globales en programas normales se complican en un programa con hilos, porque
ahora varios hilos pueden modificar simultáneamente una variable global. Para
reolver este problema se utilizan mecanismos de sincronización implementados en
la API de Win32, que ayudan a garantizar el acceso exclusivo a las variables
globales.

Ejemplo 1
Para algunas personas la idea de crear multiples hilos en un proceso
requiere de cierta preparacion mental. Comencemos por un simple ejemplo de
cómo trabajan los hilos en la API Win32.
El código siguiente contiene un ejemplo de un simple hilo. En este
programa el código imprime el valor de una variable global llamada contador
cada vez que el usuario pulsa una tecla. Nada en el programa cambia el valor de
la variable y siempre se imprime cero (0):

#include <windows.h>
#include <iostream.h>

UINT contador;

void main(void)
{
CHAR retStr[100];

contador=0;
while(1)
{
cout << "Pulsa <ENTER> para ver el valor del contador ... ";
cin.getline(retStr, 100);
cout << "El valor es: " << contador << endl << endl;
}
}

Ahora añadiremos un hilo al código mostrado anteriormente. Un hilo es una


función simple que se ejecuta en segundo plano. La función CountThread en el
código incrementa el valor de la variable global llamada contador y se duerme
100 ms. Cuando se ejecuta el programa, vemos que cada vez que pulsamos una
tecla el valor se ha incrementado (debido a la ejecucion en segundo plano del
hilo), mientras que el programa principal responde a las entradas del usuario. El
programa realiza dos cosas a la vez:
#include <windows.h>
#include <iostream.h>

volatile UINT contador;

void CountThread()
{
while(1)
{
contador++;
Sleep(100);
}
}

void main(void)
{
HANDLE countHandle;
DWORD threadID;
CHAR retStr[100];
contador=0;

// Crea un hilo que ejecuta


// la función "CountThread"

countHandle=CreateThread(0, 0,
(LPTHREAD_START_ROUTINE) CountThread,
0, 0, &threadID);

if (countHandle==0)
cout << "No se puede crear el hilo: " << GetLastError() << endl;

while(1)
{
cout << "Pulsa <ENTER> para ver el valor del contador... ";
cin.getline(retStr, 100);
cout << "El valor es: " << contador << endl << endl;
}
}
La secuencia del programa ppal., el hilo y la variable global se muestran a
continuación:
El código comienza creando un hilo con la función CreateThread de la API.
CreateThread acepta como parametro el nombre de la funcion que se ejecutará
en 2º plano (thread function), esta se llama CountThread. CreateThread devuelve
el identificador y manejador del hilo creado.
También es posible activar el hilo si , de inicio , está suspendido. Debemos
decir que un hilo no consume CPU si se encuentra suspendido.
La función Sleep es una forma eficiente de realizar un retardo. Existen
otros mecanismos como lo bucles, pero todos ellos consumen ciclos de CPU.

Hay que resaltar el uso del modificador volatile en la variable global


contador. Si ponemos como comentario la funcion Sleep en el hilo y eliminamos
el modificador volatile , contador puede ser siempre cero cualdo se pulse la tecla
[enter]. Esto es debido a ‘extraños efectos laterales’ debidos a las optimizaciones
realizadas por el compilador. El compilador, por ejemplo, puede utilizar un registro
de la CPU para almacenar el valor de la variable contador del programa principal.
De esta forma los cambios producidos por el hilo son ignorados. El modificador
volatile es una forma de decirle al compilador que no queremos ninguna
optimización aplicada a esta variable (como el almacenamiento en un registro) y
que su valor puede cambiar. volatile es un modificador muy importante si se
emplean variables globales en hilos.

Ejemplo 2
El codigo mostrado abajo representa cómo pasar un parametro (integer) a
la función del hilo y cómo esperar la finalizacion del hilo.

#include <windows.h>
#include <stdlib.h>
#include <iostream.h>

// Funcion del Hilo

void HonkThread(DWORD iter)


{
DWORD i;

for (i=0; i < iter; i++)


{
Beep(200, 50);
Sleep(1000);
}
}
void main(void)
{
HANDLE honkHandle;
DWORD threadID;
DWORD iterations;
CHAR iterStr[100];

cout << "Enter the number of beeps to produce: ";

cin.getline(iterStr, 100);

iterations=atoi(iterStr);

// crea un hilo que


// ejecuta la funcion "HonkThread"

honkHandle=CreateThread(0, 0,
(LPTHREAD_START_ROUTINE) HonkThread,
(VOID *) iterations, 0, &threadID);

// espera hasta que el hilo haya terminado

int contador=0;
while ( WaitForSingleObject(honkHandle, 0)== WAIT_TIMEOUT)
{
cout << "esperando la finalizacion del hilo"<< contador++ << endl;
}
}

Cuando se ejecuta el codigo anterior se debe pedir un valor entero (p.ej. 5)


cuando el programa lo requiera. El programa principal comenzará el hilo pasando
el valor 5 como argumento. El hilo se ejecutará en segundo plano, pitará 5 veces y
terminará. Mientras tanto el programa principal estará esperando la finalizacion del
hilo usando la funcion WaitForSingleObject dentro de un bucle. Cada vez pe
pasamos por el bucle se incrementa la variable contador y la presentamos por
pantalla.
La función WaitForSingleObject recibe como argumentos el manejador del
hilo y un valor entero de time-out el retorno inmediato de la funcion. Esta devuelve
el valor WAIT_TIMEOUT si el hilo no ha terminado. Con esta configuracion
detectamos si el hilo a terminado o no.

Es posible pasar estructuras a la funcion del hilo pasando un puntero a la


estructura como parámetro, tal y como se describe a continuación. La estructura
debe ser una variable global o declarada desde el heap.

#include <windows.h>
#include <stdlib.h>
#include <iostream.h>

typedef struct
{
DWORD frequencia
DWORD duracion;
DWORD iteracion;
} honkParams;

void HonkThread(honkParams *params)


{
DWORD i;

for (i=0; i < params->iteracion; i++)


{
Beep(params->frequencia, params->duracion);
Sleep(1000);
}
}

void main(void)
{
HANDLE honkHandle;
DWORD threadID;
honkParams params;
CHAR freqStr[100];
CHAR durStr[100];
CHAR iterStr[100];

cout << "Introducir la frecuencia a reproducir: ";


cin.getline(freqStr, 100);
params.frequencia=atoi(freqStr);

cout << "Introducir la duracion: ";


cin.getline(durStr, 100);
params.duracion=atoi(durStr);

cout << "Introducir el numero de beeps a reproducir: ";


cin.getline(iterStr, 100);
params.iteracion=atoi(iterStr);

// create a thread and pass it the address of


//the "params" structure

honkHandle=CreateThread(0, 0,
(LPTHREAD_START_ROUTINE) HonkThread,
&params, 0, &threadID);

WaitForSingleObject(honkHandle, INFINITE);
}

En el anterior codigo los tres valores introducidos por el usuario son puestos
en la estructura que es pasada el hilo. En el programa principal se llama a la
funcion WaitForSingleObject para evitar la finalizacion antes de que el hilo haya
completado su ejecucion. Sin esta llamada el programa principal terminaria y
'mataria' al proceso del hilo.

Ejemplo 3
El siguiente codigo es mas completo y muestra como es posible crear
multiples hilos, declarando varias funciones de hilos o llamando a un hilo varias
veces.

#include <windows.h>
#include <stdlib.h>
#include <iostream.h>

typedef struct
{
DWORD frequencia
DWORD duracion;
DWORD iteracion;
} honkParams;

void HonkThread(honkParams *params)


{
DWORD i;

for (i=0; i < params->iteracion; i++)


{
Beep(params->frequencia, params->duracion);
Sleep(1000);
}

GlobalFree(params);
}

void main(void)
{
HANDLE honkHandles[3]; // definimos tres manejadores
DWORD threadID;
honkParams *params;
DWORD count;
CHAR freqStr[100];
CHAR durStr[100];
CHAR iterStr[100];

for (count=0; count < 3; count++)


{
// reserva de memoria para la estructura
params=(honkParams *) GlobalAlloc(GPTR, sizeof(honkParams));

cout << "Introducir la frecuencia: ";


cin.getline(freqStr, 100);
params->frequencia=atoi(freqStr);

cout << "Introducir la duracion: ";


cin.getline(durStr, 100);
params->duracion=atoi(durStr);

cout << "Introducir numero de beeps: ";


cin.getline(iterStr, 100);
params->iteracion=atoi(iterStr);

// create a thread and pass it the pointer


// to its "params" struct

honkHandles[count]=CreateThread(0, 0,
(LPTHREAD_START_ROUTINE) HonkThread,
//duda, viene sin &!!! &params, 0, &threadID);
}

// wait for all threads to finish execution


WaitForMultipleObjects(3, honkHandles, TRUE, INFINITE);
}

Cuando ejecutamos el codigo anterior se nos piden los valores y podremos


comprobar que se oiran los beeps de los tres hilos simultáneamente y si usamos
una duracion suficientemente larga hasta se solaparán.
La funcion WaitForMultipleObjects hace lo mismo que WaitForSingleObject,
pero podemos especificar el numero de eventos que debe esperar (en este caso
los manejadores).

You might also like