10 - Ejemplos Reales y Casos de Uso en Windows

Reunimos aplicaciones habituales de la concurrencia en Windows: pools de hilos, productor-consumidor, cálculos paralelos, simulación de eventos y procesamiento de archivos. Cerramos con un mini servidor de tareas implementado en C usando un thread pool.

10.1 Pool de hilos simple sobre Windows

Un thread pool mantiene un conjunto fijo de hilos listos para ejecutar trabajos sin pagar el costo de crearlos y destruirlos cada vez. Los trabajos se encolan y los hilos libres los toman.
Reduce la latencia de tareas cortas y mantiene estable el uso de recursos frente a oleadas de solicitudes.

10.2 Productor-consumidor con semáforos y condition variables

Combinar semáforos para contar slots y condition variables para despertar hilos cuando cambian los estados permite buffers acotados eficientes, sin busy waiting.
En Windows, los semáforos pueden coordinar cupos y las condition variables reducen el tiempo bloqueado innecesario.

10.3 Paralelización de un cálculo matemático (ej: suma de arreglos)

Dividir un arreglo grande en segmentos y asignar cada segmento a un hilo reduce el tiempo total. Se sincroniza al final con WaitForMultipleObjects o una barrera.
Para un balance parejo, calcula el rango de cada hilo en función del tamaño del arreglo y el número de hilos.

10.4 Simulación de múltiples sensores o fuentes de eventos

Cada hilo simula un sensor que emite eventos periódicos. Un hilo agregador los consume y los registra o procesa, coordinando con colas seguras.
Se pueden variar frecuencias y prioridades para imitar sensores críticos frente a sensores de diagnóstico.

10.5 Procesamiento paralelo de archivos o logs en el sistema de archivos de Windows

Varios hilos leen archivos en paralelo para parsear y transformar datos. Se controla el número de hilos para no saturar I/O y se sincroniza el agregado de resultados.
Usa colas para distribuir rutas de archivo y un lock dedicado para consolidar estadísticas o escribir salidas.

10.6 Aplicación en C (CLion, Windows): Mini servidor de tareas con thread pool

El programa crea un pool de hilos fijo, mantiene una cola de tareas protegida con CRITICAL_SECTION y usa CONDITION_VARIABLE para notificar tareas nuevas. El usuario encola trabajos desde la consola; los hilos las ejecutan en paralelo.
Las tareas incluyen sumas simples y transformación de texto, pero puedes agregar procesamiento de archivos pequeños o simulaciones ligeras.

#include <windows.h>
#include <process.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX_TAREAS 64
#define POOL_SIZE 4

typedef enum { TAREA_SUMA, TAREA_MAYUS, TAREA_QUIT } TipoTarea;

typedef struct {
  TipoTarea tipo;
  int a, b;
  char texto[64];
} Tarea;

static Tarea cola[MAX_TAREAS];
static int head = 0;
static int tail = 0;
static int count = 0;
static CRITICAL_SECTION cs;
static CONDITION_VARIABLE cvNoVacia;
static CONDITION_VARIABLE cvNoLlena;
static volatile LONG detener = 0;

static int seguir(void) {
  return InterlockedCompareExchange(&detener, 0, 0) == 0;
}

static void encolar(const Tarea *t) {
  cola[tail] = *t;
  tail = (tail + 1) % MAX_TAREAS;
  count++;
}

static int desencolar(Tarea *t) {
  if (count == 0) return 0;
  *t = cola[head];
  head = (head + 1) % MAX_TAREAS;
  count--;
  return 1;
}

static void procesar_tarea(const Tarea *t) {
  switch (t->tipo) {
    case TAREA_SUMA:
      printf("[pool] suma %d + %d = %d\n", t->a, t->b, t->a + t->b);
      break;
    case TAREA_MAYUS: {
      char buffer[64];
      size_t i = 0;
      for (; i < sizeof(buffer) - 1 && t->texto[i]; i++) {
        char c = t->texto[i];
        buffer[i] = (char)((c >= 'a' && c <= 'z') ? (c - 'a' + 'A') : c);
      }
      buffer[i] = '\\0';
      printf("[pool] mayus: %s\n", buffer);
      break;
    }
    case TAREA_QUIT:
      /* manejada en el hilo principal */
      break;
  }
}

unsigned __stdcall trabajador(void *arg) {
  int id = (int)(intptr_t)arg;
  (void)id;
  while (seguir()) {
    EnterCriticalSection(&cs);
    while (count == 0 && seguir()) {
      SleepConditionVariableCS(&cvNoVacia, &cs, INFINITE);
    }
    if (!seguir()) {
      LeaveCriticalSection(&cs);
      break;
    }
    Tarea t;
    (void)desencolar(&t);
    WakeConditionVariable(&cvNoLlena);
    LeaveCriticalSection(&cs);

    procesar_tarea(&t);
  }
  return 0;
}

static void encolar_suma(int a, int b) {
  Tarea t = { .tipo = TAREA_SUMA, .a = a, .b = b };
  encolar(&t);
}

static void encolar_mayus(const char *texto) {
  Tarea t = { .tipo = TAREA_MAYUS, .a = 0, .b = 0 };
  snprintf(t.texto, sizeof(t.texto), "%s", texto);
  encolar(&t);
}

int main(void) {
  InitializeCriticalSection(&cs);
  InitializeConditionVariable(&cvNoVacia);
  InitializeConditionVariable(&cvNoLlena);

  HANDLE hilos[POOL_SIZE] = {0};
  for (int i = 0; i < POOL_SIZE; i++) {
    uintptr_t h = _beginthreadex(NULL, 0, trabajador, (void *)(intptr_t)i, 0, NULL);
    hilos[i] = (HANDLE)h;
  }

  puts("Mini servidor de tareas. Comandos:");
  puts("  s a b   -> suma a+b");
  puts("  m texto -> convierte a MAYUS");
  puts("  q       -> salir");

  char linea[128];
  while (seguir() && fgets(linea, sizeof(linea), stdin)) {
    if (linea[0] == 'q') {
      break;
    } else if (linea[0] == 's') {
      int a, b;
      if (sscanf(linea, "s %d %d", &a, &b) == 2) {
        EnterCriticalSection(&cs);
        while (count == MAX_TAREAS) {
          SleepConditionVariableCS(&cvNoLlena, &cs, INFINITE);
        }
        encolar_suma(a, b);
        WakeConditionVariable(&cvNoVacia);
        LeaveCriticalSection(&cs);
      } else {
        puts("Formato: s a b");
      }
    } else if (linea[0] == 'm') {
      char texto[64] = {0};
      if (sscanf(linea, "m %63[^\n]", texto) == 1) {
        EnterCriticalSection(&cs);
        while (count == MAX_TAREAS) {
          SleepConditionVariableCS(&cvNoLlena, &cs, INFINITE);
        }
        encolar_mayus(texto);
        WakeConditionVariable(&cvNoVacia);
        LeaveCriticalSection(&cs);
      } else {
        puts("Formato: m texto");
      }
    } else {
      puts("Comando no reconocido.");
    }
  }

  InterlockedExchange(&detener, 1);
  EnterCriticalSection(&cs);
  WakeAllConditionVariable(&cvNoVacia);
  LeaveCriticalSection(&cs);

  WaitForMultipleObjects(POOL_SIZE, hilos, TRUE, INFINITE);
  for (int i = 0; i < POOL_SIZE; i++) {
    CloseHandle(hilos[i]);
  }
  DeleteCriticalSection(&cs);
  return 0;
}

Prueba con varias tareas seguidas para ver cómo el pool reparte el trabajo. Ajusta POOL_SIZE y MAX_TAREAS según la carga y el costo de cada tarea.
Si las tareas tardan mucho, agrega timeouts o cancelación para evitar que el pool quede ocupado indefinidamente.

Buenas Prácticas en Programación con Hilos en Windows

  • Define un orden de locks y respétalo siempre.
  • Usa timeouts en esperas potencialmente infinitas para detectar bloqueos.
  • Prefiere CRITICAL_SECTION dentro del mismo proceso; reserva mutex para sincronización entre procesos.
  • Ajusta las prioridades con cuidado para evitar starvation.
  • Libera handles y recursos de kernel tan pronto como no se usen.
  • Registra (log) las transiciones clave para depurar interleavings complejos.