480 likes | 672 Views
Programando con Hilos de Windows*. Intel Software College. Objetivos. Al término de este módulo, será capaz de: Escribir programas para crear y terminar hilos Usar objetos de sincronización para coordinar la ejecución entre hilos y accesos a memoria. Agenda.
E N D
Programando con Hilos de Windows* Intel Software College
Objetivos • Al término de este módulo, será capaz de: • Escribir programas para crear y terminar hilos • Usar objetos de sincronización para coordinar la ejecución entre hilos y accesos a memoria Programming with Windows Threads
Agenda • Explorar las funciones del API para hilos en Win32 • Crear hilos • Esperar que los hilos terminen • Sincronizar acceso compartido entre hilos Prácticas para experiencia práctica Programming with Windows Threads
Windows* tipo HANDLE • Cada objeto de windows es referenciado por variables de tipo HANDLE • Apuntador a objetos del kernel • Hilo, proceso, archivo, evento, mutex, semáforo, etc. • Las funciones para crear objetos devuelven un HANDLE • Los objetos se controlan a través de su HANDLE • No se manipulan directamente los objetos Programming with Windows Threads
Creación de Hilos en Windows* HANDLE CreateThread( LPSECURITY_ATTRIBUTES ThreadAttributes, DWORD StackSize, LPTHREAD_START_ROUTINE StartAddress, LPVOID Parameter, DWORD CreationFlags, LPDWORD ThreadId ); // Out Programming with Windows Threads
LPTHREAD_START_ROUTINE • CreateThread() espera un apuntador a una función global • Devuelve DWORD • Convención de llamadas de WINAPI • Un solo parámetro LPVOID (void *) • El hilo inicia la ejecución de la función DWORD WINAPI MyThreadStart(LPVOID p); Programming with Windows Threads
Usando Hilos Explícitamente • Identifica porciones de código a paralelizar • Encapsula código en una función • Si el código ya es una función, una función de un driver puede necesitar ser escrita para coordinar el trabajo de varios hilos • Añadir la llamada CreateThread para asignar hilos para ejecutar la función Programming with Windows Threads
Destruyendo Hilos • Libera recursos del SO • Limpia si se terminó el trabajo con el hilo antes de que el programa termine • La terminación del proceso lo hace • BOOL CloseHandle(HANDLE hObject); Programming with Windows Threads
#include <stdio.h> #include <windows.h> DWORD WINAPI helloFunc(LPVOID arg ) { printf(“Hello Thread\n”); return 0; } main() { HANDLE hThread = CreateThread(NULL, 0, helloFunc, NULL, 0, NULL ); } Ejemplo: Creación de Hilos ¿Qué Sucede? Programming with Windows Threads
Explicación del Ejemplo • El hilo principal es un proceso • Cuando el proceso termina, todos los hilos terminan • Se requiere algún método para esperar que un hilo termine Programming with Windows Threads
#include <stdio.h> #include <windows.h> BOOL threadDone = FALSE ; DWORD WINAPI helloFunc(LPVOID arg ) { printf(“Hello Thread\n”); threadDone = TRUE ; return 0; } main() { HANDLE hThread = CreateThread(NULL, 0, helloFunc, NULL, 0, NULL ); while (!threadDone); } Esperando un Hilo de Windows* No es una buena idea! // ciclos desperdiciados! Programming with Windows Threads
Esperando un Hilo • Espera un objeto (hilo) • DWORD WaitForSingleObject( • HANDLE hHandle, • DWORD dwMilliseconds ); • El hilo creador de hilos espera (bloqueado) hasta • El tiempo expira • Regresa un código para indicarlo • El hilo sale (se le indica al manejador) • Usa INFINITE para esperar hasta la terminación del hilo • No usa ciclos del CPU Programming with Windows Threads
Esperando Muchos Hilos • Espera hasta 64 objetos (hilos) • DWORD WaitForMultipleObjects( • DWORD nCount, • CONST HANDLE *lpHandles, // arreglo • BOOL fWaitAll, // espera uno o todos • DWORD dwMilliseconds) • Espera a todos: fWaitAll==TRUE • Espera a cualquiera: fWaitAll==FALSE • El valor de retorno es el primer índice del arreglo encontrado Programming with Windows Threads
Detalles en las funciones WaitFor* • Descriptor (Handle) como parámetro • Usado para diferentes tipos de objetos • Los objetos del kernel tienen dos estados • Signaled • Non-signaled • El comportamiento se define por el objeto referido por el manejador • Hilo: signaled indica terminado Programming with Windows Threads
#include <stdio.h> #include <windows.h> const int numThreads = 4; DWORD WINAPI helloFunc(LPVOID arg ) { printf(“Hello Thread\n”); return 0; } main() { HANDLE hThread[numThreads]; for (int i = 0; i < numThreads; i++) hThread[i] = CreateThread(NULL, 0, helloFunc, NULL, 0, NULL ); WaitForMultipleObjects(numThreads, hThread, TRUE, INFINITE); } Ejemplo: Varios Hilos Programming with Windows Threads
Activdad 1 - “HelloThreads” • Modifica el ejemplo previo para para mostrar • El mensaje “Hello Thread” apropiaido • Número de hilo único • Usa el la variable del ciclo for del CreateThread • Salida ejemplo: • Hello from Thread #0 • Hello from Thread #1 • Hello from Thread #2 • Hello from Thread #3 Programming with Windows Threads
¿Qué es Incorrecto? • ¿Qué se muestra en myNum? DWORD WINAPI threadFunc(LPVOID pArg) { int* p = (int*)pArg; int myNum = *p; printf( “Thread number %d\n”, myNum); } . . . // from main(): for (int i = 0; i < numThreads; i++) { hThread[i] = CreateThread(NULL, 0, threadFunc, &i, 0, NULL); } Programming with Windows Threads
for(int i=0;i<numThreads;i++) { CreateThread(NULL, 0, threadFunc, &i, 0, NULL); } Contenido de la dirección 0x0001004 DWORD WINAPI threadFunc(LPVOID pArg) { int* p = (int*)pArg; int myNum = *p; printf( “Thread number %d\n”, myNum); } i=0x0001004 0 1 pArg=0x0001008 0x0001004 1 p=0x000100C mynum=0x0001010 1 Programming with Windows Threads
Línea de Tiempo Hello “Threads” Programming with Windows Threads
Condiciones de Concurso • Varios hilos acceden la misma variable de manera concurrente • Conflicto Lectura/Escritura • Conflicto Escritura/Escritura • El error más común en programas concurrentes • No siempre es obvio Programming with Windows Threads
¿Cómo Evitar Condiciones de Concurso? • El alcance de las variables local en los hilos • Variables declaradas dentro de las funciones de los hilos • Se almacenan en el stack del hilo • TLS (Thread Local Storage) • Controla el acceso compartido con regiones críticas • Exclusión mutua y sincronización • Lock, semáforo, evento, sección crítica, mutex … Programming with Windows Threads
Solución – Almacenamiento “Local” DWORD WINAPI threadFunc(LPVOID pArg) { int myNum = *((int*)pArg); printf( “Thread number %d\n”, myNum); } . . . // from main(): for (int i = 0; i < numThreads; i++) { tNum[i] = i; hThread[i] = CreateThread(NULL, 0, threadFunc, &tNum[i], 0, NULL); } Programming with Windows Threads
Windows* Mutexes • Objeto del kérnel que se referencía por un descriptor • “Signaled” cuando está disponible • Operaciones: • CreateMutex(…) // crear nuevo • WaitForSingleObject // wait y lock • ReleaseMutex(…) // unlock • Disponible entre procesos Programming with Windows Threads
Sección Crítica en Windows* • Ligero, entre procesos solo mutex • El más útil y más usado • Nuevo tipo • CRITICAL_SECTION cs; • Operaciones de crear y destruir • InitializeCriticalSection(&cs) • DeleteCriticalSection(&cs); Programming with Windows Threads
Sección Crítica en Windows* • CRITICAL_SECTIONcs ; • Intenta entrar al código protegido • EnterCriticalSection(&cs) • Se bloquea si otro hilo está en la sección crítica • Regresa cuando no hay hilos en la sección crítica • Al salir de la sección crítica • LeaveCriticalSection(&cs) • Debe ser desde el hilo que la obtiene Programming with Windows Threads
#define NUMTHREADS 4 CRITICAL_SECTION g_cs; // ¿Por qué tiene que ser global? int g_sum = 0; DWORD WINAPI threadFunc(LPVOID arg ) { int mySum = bigComputation(); EnterCriticalSection(&g_cs); g_sum += mySum; // Los hilos acceden una a la vez LeaveCriticalSection(&g_cs); return 0; } main() { HANDLE hThread[NUMTHREADS]; InitializeCriticalSection(&g_cs); for (int i = 0; i < NUMTHREADS; i++) hThread[i] = CreateThread(NULL,0,threadFunc,NULL,0,NULL); WaitForMultipleObjects(NUMTHREADS, hThread, TRUE, INFINITE); DeleteCriticalSection(&g_cs); } Ejemplo: Sección Crítica Programming with Windows Threads
f(x) = 1 4.0 dx = (1+x2) 0 4.0 (1+x2) Ejemplo de Integración Numérica 4.0 static long num_steps=100000; double step, pi; void main() { int i; double x, sum = 0.0; step = 1.0/(double) num_steps; for (i=0; i< num_steps; i++){ x = (i+0.5)*step; sum = sum + 4.0/(1.0 + x*x); } pi = step * sum; printf(“Pi = %f\n”,pi); } 2.0 0.0 1.0 X Programming with Windows Threads
Actividad 2 - Calculando Pi • Paraleliza la integración numérica usando hilos de Windows* • Como pueden las iteraciones de los ciclos dividirse entre los hilos • ¿Qué variables pueden ser locales? • ¿Qué variables necesitan ser visibles a todos los hilos? static long num_steps=100000; double step, pi; void main() { int i; double x, sum = 0.0; step = 1.0/(double) num_steps; for (i=0; i< num_steps; i++){ x = (i+0.5)*step; sum = sum + 4.0/(1.0 + x*x); } pi = step * sum; printf(“Pi = %f\n”,pi); } Programming with Windows Threads
Eventos de Windows* • Usados para enviarle a otros hilos señales de que ha ocurrido un evento • Ejemplo: los datos están disponibles, el mensaje está listo • Los hilos esperan señales con la función WaitFor* • Dos tipos de eventos • Auto-reset • Manual-reset Programming with Windows Threads
El evento permanece signaled hasta que un hilo espera y se libera Si no hay hilos esperando, el estado permanece signaled Una vez que el hilo se libera, el estado se restablece a not-signaled El evento permanece signaled hasta que es reiniciado por una llamada de la API Varios hilos pueden esperar y ser liberados Los hilos que originalmente están en espera pueden iniciar y ser liberados Tipos de Eventos Auto-reset Manual-reset Precaución: Sea cuidadoso cuando se usa WaitForMultipleObjects para esperar TODOS los eventos Programming with Windows Threads
Creación de Eventos en Windows* • HANDLE CreateEvent( • LPSECURITY_ATTRIBUTES lpEventAttributes, • BOOL bManualReset, // TRUE => manual reset • BOOL bInitialState, // TRUE => begin signaled • LPCSTR lpName); // text name for object • Establecer bManualReset a TRUE para un evento “manual-reset event”; FALSE para un evento “auto-reset” • Establecer bInitialState a TRUE para que un evento inicie en estado “signaled”; FALSE para iniciar “unsignaled” Programming with Windows Threads
Establecer y Reiniciar Eventos • Establecer un evento a un estado signaled • BOOL SetEvent( HANDLE event ); • Reiniciar un evento manualmente • BOOL ResetEvent( HANDLE event ); • Pulsar evento • BOOL PulseEvent( HANDLE event ); Programming with Windows Threads
Ejemplo: Un Hilo Buscador • Un hilo creado busca un elemento • Manda una señal si el elemento se encuentra • El hilo principal (Main Thread) espera la señal y terminación del hilo • Muestra un mensaje si el elemento se encuentra • Muestra mensaje hasta la terminación del hilo • Ilustra • Usando el tipo HANDLE genérico • No esperar que cada objeto haga un signal Programming with Windows Threads
Eejemplo: Eventos • DWORD WINAPI threadFunc(LPVOID arg) { • BOOL bFound = bigFind() ; • if (bFound) • { • SetEvent(hObj[0]); // señal, el dato fue encontrado • bigFound() ; • } • moreBigStuff() ; • return 0; • } HANDLE hObj[2]; // 0 es evento, 1 es hilo Programming with Windows Threads
Ejempo: Función Principal • . . . • hObj[0] = CreateEvent(NULL, FALSE, FALSE, NULL); • hObj[1] = CreateThread(NULL,0,threadFunc,NULL,0,NULL); • /* Hacer otra cosa mientras el hilo realiza la búsqueda */ • DWORD waitRet = • WaitForMultipleObjects(2, hObj, FALSE, INFINITE); • switch(waitRet) { • case WAIT_OBJECT_0: // señal del evento • printf("found it!\n"); • WaitForSingleObject(hObj[1], INFINITE) ; • // • case WAIT_OBJECT_0+1: // señal del hilo • printf("thread done\n"); • break ; • default: • printf("wait error: ret %u\n", waitRet); • break ; • } • . . . Programming with Windows Threads
Ejempo: Función Principal • . . . • hObj[0] = CreateEvent(NULL, FALSE, FALSE, NULL); • hObj[1] = CreateThread(NULL,0,threadFunc,NULL,0,NULL); • /* Do some other work while thread executes search */ • DWORD waitRet = • WaitForMultipleObjects(2, hObj, FALSE, INFINITE); • switch(waitRet) { • case WAIT_OBJECT_0: // señal del evento • printf(“encontrado!\n"); • WaitForSingleObject(hObj[1], INFINITE) ; • // fall thru • case WAIT_OBJECT_0+1: // señal del hilo • printf(“hilo terminado\n"); • break ; • default: • printf("wait error: ret %u\n", waitRet); • break ; • } • . . . Programming with Windows Threads
Actividad 3 – Usando Eventos • Remplazar el spin-wait y la variable contador de hilos con eventos para enviar señal de terminación de hilo de la pieza computacional Programming with Windows Threads
Semáforos de Windows* • Objetos de sincronización que contienen un contador • Representa el número de recursos disponibles • Formalizados por Edsger Dijkstra (1968) • Dos operaciones en los semáforos • Wait [P(s)]: El hilo espera hasta que s > 0, entonces s = s-1 • Post [V(s)]: s = s + 1 • El semáforo está en estado signaled si el contador > 0 Programming with Windows Threads
Win32* Creación de Semáforos • HANDLE CreateSemaphore( • LPSECURITY_ATTRIBUTES lpEventAttributes, • LONG lSemInitial, // Initial count value • LONG lSemMax, // Maximum value for count • LPCSTR lpSemName); // text name for object • Valor de lSemMax debe ser 1 o mayor • Valor de lSemInitialdebe ser • Mayor o igual a cero, • Menor o igual que lSemMax,y • No puede estar fuera del rango Programming with Windows Threads
Operaciones Wait y Post • Usa WaitForSingleObject para esperar en un semáforo • Si el contador es == 0, el hilo espera • Decrementa el contador en 1 cuando el contador > 0 • Incrementa el semáforo (Operación Post) • BOOL ReleaseSemaphore( • HANDLE hSemaphore, • LONG cReleaseCount, • LPLONG lpPreviousCount ); • Incrementa el contador del semáforo según el valor de cReleaseCount • Devuelve el valor previo del contador a través de lpPreviousCount Programming with Windows Threads
Aplicaciones de los Semáforos • Controlar el acceso a estructuras de datos de tamaño limitado • Colas, stacks, deques • Usa un contador para enumerar elementos disponibles • Controla el acceso a un número finito de recursos • Descriptores de archivos, unidades de cinta, … • Regula la cantidad de hilos activos dentro de una región • Un semáforo binario [0,1] puede funcionar como un mutex Programming with Windows Threads
Cuidados con el uso de Semáforos • No hay propietario del semáforo • Cualquier hilo puede liberar un semáforo, no solo el ultimo hilo que realizó el wait • Usar una buena práctica de programación para evitar esto • No existe el concepto de semáforo abandonado • Si el hilo termina antes de realizar la operación post, se pierde el incremento del semáforo • Deadlock Programming with Windows Threads
Ejemplo: Semáforo como Mutex • El hilo principal abre el archivo de entrada, espera la terminación del hilo • Los hilos • Leen la línea del archivo de entrada • Cuentan todas las palabras de cinco letras en una línea Programming with Windows Threads
Ejemplo: Principal HANDLE hSem1, hSem2; FILE *fd; int fiveLetterCount = 0; • main() • { HANDLE hThread[NUMTHREADS]; • hSem1 = CreateSemaphore(NULL, 1, 1, NULL); // Binary semaphore • hSem2 = CreateSemaphore(NULL, 1, 1, NULL); // Binary semaphore • fd = fopen(“InFile”, “r”); // Open file for read • for (int i = 0; i < NUMTHREADS; i++) • hThread[i] = CreateThread(NULL,0,CountFives,NULL,0,NULL); • WaitForMultipleObjects(NUMTHREADS, hThread, TRUE, INFINITE); • fclose(fd); • printf(“Number of five letter words is %d\n”, fiveLetterCount); • } Programming with Windows Threads
Ejemplo: Semáforos • DWORD WINAPI CountFives(LPVOID arg) { • BOOL bDone = FALSE ; • char inLine[132]; int lCount = 0; • while (!bDone) • { • WaitForSingleObject(hSem1, INFINITE); // accede la entrada • bDone = (GetNextLine(fd, inLine) == EOF); • ReleaseSemaphore(hSem1, 1, NULL); • if (!bDone) • if (lCount = GetFiveLetterWordCount(inLine)) { • WaitForSingleObject(hSem2, INFINITE);//actualiza var • fiveLetterCount += lCount; • ReleaseSemaphore(hsem2, 1, NULL); • } • } • } Programming with Windows Threads
Actividad 4 – Usando Semáforos • Usar semáforos binarios para controlar el acceso a variables compartidas Programming with Windows Threads
Programando con Hilos de WindowsQue se ha Cubierto • Crear hilos para ejecutar trabajo encapsulado dentro de funciones • Lo normal de esperar hilos para terminar • Coordinar acceso compartido entre hilos para evitar condiciones de concurso • Almacenamiento local para evitar concursos • Objetos de sincronización para organizar el uso Programming with Windows Threads