4 - Estados del Hilo y Planificación (Windows)

Analizamos el ciclo de vida de un hilo en Windows, sus estados lógicos y cómo el planificador (scheduler) asigna CPU según prioridad y disponibilidad. También revisamos efectos de Sleep e I/O en el rendimiento y cerramos con un simulador de tareas para ver el interleaving (intercalado de ejecuciones).

4.1 Ciclo de vida de un hilo en Windows

Un hilo nace cuando lo crea el proceso (CreateThread o _beginthreadex), pasa a estado listo, ejecuta cuando el scheduler le asigna CPU, puede bloquearse (I/O, sincronización, Sleep) y termina al retornar o al llamar a ExitThread. Al finalizar, el handle sigue válido hasta que se cierre con CloseHandle.

4.2 Estados clásicos: running, ready, waiting, terminated (visión lógica)

  • Running: el hilo está ejecutando instrucciones en CPU.
  • Ready: puede ejecutar pero espera turno de CPU.
  • Waiting: bloqueado por I/O, evento, mutex o Sleep.
  • Terminated: ya finalizó; el sistema conserva metadatos hasta que el proceso cierre el handle.

4.3 Prioridades de hilos en Windows: SetThreadPriority()

Cada hilo tiene una prioridad base derivada de la clase del proceso. SetThreadPriority permite subirla o bajarla (por ejemplo, THREAD_PRIORITY_ABOVE_NORMAL o THREAD_PRIORITY_BELOW_NORMAL). Elevar prioridad puede reducir latencia de un hilo, pero abusar genera inanición (starvation) en hilos de menor prioridad.

4.4 Hilos bloqueados por I/O, Sleep() o primitivas de sincronización

Cuando un hilo espera I/O o llama a Sleep, cede la CPU y el scheduler ejecuta otro hilo listo. Las primitivas de sincronización (WaitForSingleObject, eventos, mutex) también estacionan el hilo en estado waiting. Usar Sleep(0) cede voluntariamente el quantum actual.

4.5 Efecto de la planificación y la prioridad en el rendimiento

Un sistema con muchos hilos listos genera más cambios de contexto y menor afinidad de cache. Prioridades altas pueden acaparar CPU y desplazar tareas de fondo; prioridades bajas pueden volverse intermitentes si siempre hay hilos más prioritarios. Medir y ajustar prioridades debe hacerse con cuidado.

4.6 Aplicación en C (CLion, Windows): Simulador simple de tareas con Sleep

El programa crea varios hilos que mezclan trabajo falso (ciclo de CPU) y pausas con Sleep. Cada hilo registra el tiempo de inicio y fin con GetTickCount64 para observar el interleaving. Se usa _beginthreadex y WaitForMultipleObjects.

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

#define MAX_HILOS 8

typedef struct {
  int id;
  int sleep_ms;
  int work_loops;
} Task;

void trabajo_falso(int vueltas) {
  volatile unsigned long acc = 0;
  for (int i = 0; i < vueltas; i++) {
    acc += (unsigned long)(i * 3u);
  }
}

unsigned __stdcall hilo_trabajo(void *arg) {
  Task *t = (Task *)arg;
  ULONGLONG inicio = GetTickCount64();
  printf("[hilo %d] inicio en %llu ms\n", t->id, (unsigned long long)inicio);

  trabajo_falso(t->work_loops);
  Sleep(t->sleep_ms);
  trabajo_falso(t->work_loops / 2);

  ULONGLONG fin = GetTickCount64();
  printf("[hilo %d] fin en %llu ms (duracion aproximada %llu ms)\n",
         t->id,
         (unsigned long long)fin,
         (unsigned long long)(fin - inicio));
  return 0;
}

int main(void) {
  Task tareas[MAX_HILOS] = {
    {0, 100, 500000},
    {1, 200, 600000},
    {2, 400, 300000},
    {3, 50,  800000},
    {4, 0,   900000},
    {5, 150, 700000},
    {6, 250, 400000},
    {7, 350, 500000},
  };

  HANDLE handles[MAX_HILOS] = {0};

  for (int i = 0; i < MAX_HILOS; i++) {
    uintptr_t h = _beginthreadex(
      NULL,
      0,
      hilo_trabajo,
      &tareas[i],
      0,
      NULL
    );
    if (h == 0) {
      fprintf(stderr, "No se pudo crear el hilo %d\n", i);
      return EXIT_FAILURE;
    }
    handles[i] = (HANDLE)h;
  }

  DWORD espera = WaitForMultipleObjects(MAX_HILOS, handles, TRUE, INFINITE);
  if (espera == WAIT_FAILED) {
    fprintf(stderr, "WaitForMultipleObjects fallo (%lu)\n", GetLastError());
  }

  for (int i = 0; i < MAX_HILOS; i++) {
    CloseHandle(handles[i]);
  }

  puts("Todos los hilos finalizaron. Revisa el log de inicio/fin arriba.");
  return EXIT_SUCCESS;
}

Puedes ajustar sleep_ms y work_loops para ver cómo cambia el interleaving. Si quieres experimentar con prioridades, llama a SetThreadPriority sobre uno de los handles antes de esperar y observa el efecto en los tiempos.

Log de hilos en Windows mostrando intercalado de ejecuciones con distintos Sleep y prioridades