Las variables de condición permiten que un hilo duerma hasta que se cumpla un evento (por ejemplo, que la cola ya no esté vacía). Windows provee CONDITION_VARIABLE para usarse junto con CRITICAL_SECTION o SRWLOCK, logrando esperas y desbloqueos atómicos sin ocupar CPU.
Es un objeto ligero de usuario que permite que un hilo espere a que otro lo despierte cuando una condición lógica se cumpla. No almacena estado de la condición; solo coordina las esperas y despertares.
CONDITION_VARIABLE e interacción con CRITICAL_SECTIONSe inicializa con InitializeConditionVariable. Para usarla, se protege el recurso con una CRITICAL_SECTION; el hilo libera el lock y duerme de manera atómica mediante SleepConditionVariableCS. Al despertar, vuelve a poseer la sección crítica.
SleepConditionVariableCS(): espera y desbloqueo atómicoEsta función toma un puntero a la variable de condición, la sección crítica y un timeout. Su operación es atómica: suelta el lock, duerme y lo vuelve a adquirir al regresar. Si expira el timeout devuelve FALSE con ERROR_TIMEOUT.
WakeConditionVariable() y WakeAllConditionVariable()WakeConditionVariable despierta a un solo hilo en espera. WakeAllConditionVariable despierta a todos los hilos bloqueados en esa variable. No reordena la cola de espera; los hilos contendiendo deben volver a chequear la condición protegida.
CRITICAL_SECTIONEn un buffer acotado, los productores esperan si el buffer está lleno y los consumidores esperan si está vacío. Las variables de condición despiertan al rol opuesto cuando cambian los contadores, evitando busy-waiting y protegiendo la estructura con un lock.
El programa mantiene una cola circular de tareas. Varios productores encolan y varios consumidores desencolan. Los consumidores duermen con SleepConditionVariableCS si no hay datos; los productores los despiertan con WakeConditionVariable (o todos con WakeAllConditionVariable al finalizar). Toda la cola se protege con CRITICAL_SECTION.
#include <windows.h>
#include <process.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_TAREAS 16
#define PRODUCTORES 2
#define CONSUMIDORES 2
typedef struct {
int id;
int valor;
} 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(Tarea t) {
cola[tail] = t;
tail = (tail + 1) % MAX_TAREAS;
count++;
}
static Tarea desencolar(void) {
Tarea t = cola[head];
head = (head + 1) % MAX_TAREAS;
count--;
return t;
}
unsigned __stdcall productor(void *arg) {
int id = (int)(intptr_t)arg;
int secuencia = id * 1000;
while (seguir()) {
EnterCriticalSection(&cs);
while (count == MAX_TAREAS && seguir()) {
SleepConditionVariableCS(&cvNoLlena, &cs, INFINITE);
}
if (!seguir()) {
LeaveCriticalSection(&cs);
break;
}
Tarea t = { .id = id, .valor = ++secuencia };
encolar(t);
WakeConditionVariable(&cvNoVacia);
LeaveCriticalSection(&cs);
Sleep(80);
}
return 0;
}
unsigned __stdcall consumidor(void *arg) {
int id = (int)(intptr_t)arg;
while (seguir()) {
EnterCriticalSection(&cs);
while (count == 0 && seguir()) {
SleepConditionVariableCS(&cvNoVacia, &cs, INFINITE);
}
if (count == 0 && !seguir()) {
LeaveCriticalSection(&cs);
break;
}
Tarea t = desencolar();
WakeConditionVariable(&cvNoLlena);
LeaveCriticalSection(&cs);
printf("[consumidor %d] proceso tarea %d (prod %d)\n", id, t.valor, t.id);
Sleep(120);
}
return 0;
}
int main(void) {
InitializeCriticalSection(&cs);
InitializeConditionVariable(&cvNoVacia);
InitializeConditionVariable(&cvNoLlena);
HANDLE hilos[PRODUCTORES + CONSUMIDORES] = {0};
for (int i = 0; i < PRODUCTORES; i++) {
uintptr_t h = _beginthreadex(NULL, 0, productor, (void *)(intptr_t)i, 0, NULL);
hilos[i] = (HANDLE)h;
}
for (int i = 0; i < CONSUMIDORES; i++) {
uintptr_t h = _beginthreadex(NULL, 0, consumidor, (void *)(intptr_t)i, 0, NULL);
hilos[PRODUCTORES + i] = (HANDLE)h;
}
puts("Cola en marcha. Presiona una tecla para salir...");
while (!_kbhit()) {
Sleep(100);
}
_getch();
InterlockedExchange(&detener, 1);
EnterCriticalSection(&cs);
WakeAllConditionVariable(&cvNoVacia);
WakeAllConditionVariable(&cvNoLlena);
LeaveCriticalSection(&cs);
WaitForMultipleObjects(PRODUCTORES + CONSUMIDORES, hilos, TRUE, INFINITE);
for (int i = 0; i < PRODUCTORES + CONSUMIDORES; i++) {
CloseHandle(hilos[i]);
}
DeleteCriticalSection(&cs);
return EXIT_SUCCESS;
}
Puedes jugar con MAX_TAREAS, la cantidad de productores/consumidores o los Sleep para ver cómo varía el interleaving. Cambia WakeConditionVariable por WakeAllConditionVariable en el productor si quieres despertar a todos los consumidores ante cada inserción.