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).
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.
Sleep.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.
Sleep() o primitivas de sincronizaciónCuando 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.
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.
SleepEl 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.