Guía para escribir código concurrente robusto en Windows: sincroniza solo lo necesario, evita contención, documenta la propiedad de los locks, cuida la memoria compartida y usa herramientas de depuración para encontrar interleavings problemáticos.
Sincronizar de más reduce paralelismo; sincronizar de menos introduce carreras. Identifica qué datos se comparten realmente y aísla las secciones críticas al mínimo.
Prefiere operaciones atómicas (Interlocked*) cuando baste con un contador o flag y evita envolver grandes bloques de código con locks.
Haz el trabajo pesado fuera del lock: copia datos a variables locales, suelta el lock y luego procesa. Divide locks por responsabilidad (ej. lock para cola, lock para log) para reducir colas de espera.
Mide la contención con contadores de espera o registros de tiempo y ajusta el diseño si ves que los hilos pasan demasiado en espera.
CRITICAL_SECTION o mutexAgrega comentarios breves cerca de la definición: “protege cola de tareas” o “protege estadísticas”. Evita que dos locks protejan el mismo recurso sin un orden definido.
Si hay que tomar varios locks, documenta el orden global aceptado para prevenir deadlocks.
Define la propiedad de cada buffer. Usa Interlocked* para contadores simples. Evita liberar memoria que otro hilo pueda estar usando; usa refcounts atómicos o pools.
Cuando pases punteros entre hilos, establece claramente quién es responsable de liberarlos y cuándo.
Registra eventos clave con timestamps y el ID de hilo (GetCurrentThreadId). Usa OutputDebugString para ver logs en herramientas como DebugView. Activa ASSERT o _ASSERTE para invariantes. Revisa con WTSSession o ETW si necesitas trazas profundas.
Incluye contadores de reintentos o timeouts en las esperas para detectar livelocks y bloqueos silenciosos.
Partimos de una versión insegura con variables globales sin protección y logs entremezclados. Con #define MODO_CORREGIDO activado, se introducen secciones críticas, se reduce la zona protegida y se ordenan los logs para facilitar el debug.
El refactor separa responsabilidades: un lock para el contador y otro para la salida, minimizando la contención sin perder coherencia.
#include <windows.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#define HILOS 4
#define ITERACIONES 200000
/* Activa el modo seguro */
#define MODO_CORREGIDO
static volatile long contador = 0;
static CRITICAL_SECTION csContador;
static CRITICAL_SECTION csLog;
static void trabajo_pesado(void) {
for (volatile int i = 0; i < 500; i++) {
/* Simula CPU para forzar interleaving */
}
}
static void log_line(const char *msg, int hilo, long valor) {
#ifdef MODO_CORREGIDO
EnterCriticalSection(&csLog); /* Protege la salida */
#endif
printf("[hilo %d] %s (%ld)\n", hilo, msg, valor);
#ifdef MODO_CORREGIDO
LeaveCriticalSection(&csLog);
#endif
}
unsigned __stdcall worker(void *arg) {
int id = (int)(intptr_t)arg;
for (int i = 0; i < ITERACIONES; i++) {
#ifdef MODO_CORREGIDO
EnterCriticalSection(&csContador);
contador++;
long valor = contador;
LeaveCriticalSection(&csContador);
#else
contador++; /* sin protección: race */
long valor = contador; /* lectura no sincronizada */
#endif
if (i % 50000 == 0) {
log_line("avance", id, valor);
}
trabajo_pesado(); /* fuera del lock en modo seguro */
}
log_line("fin", id, contador);
return 0;
}
int main(void) {
#ifdef MODO_CORREGIDO
InitializeCriticalSection(&csContador); /* protege contador */
InitializeCriticalSection(&csLog); /* protege stdout */
#endif
HANDLE hilos[HILOS] = {0};
for (int i = 0; i < HILOS; i++) {
uintptr_t h = _beginthreadex(NULL, 0, worker, (void *)(intptr_t)i, 0, NULL);
if (h == 0) {
fprintf(stderr, "No se pudo crear el hilo %d\n", i);
return EXIT_FAILURE;
}
hilos[i] = (HANDLE)h;
}
WaitForMultipleObjects(HILOS, hilos, TRUE, INFINITE);
for (int i = 0; i < HILOS; i++) {
CloseHandle(hilos[i]);
}
#ifdef MODO_CORREGIDO
DeleteCriticalSection(&csContador);
DeleteCriticalSection(&csLog);
#endif
printf("Contador final: %ld (esperado %d)\n", contador, HILOS * ITERACIONES);
return 0;
}
Ejecuta primero sin MODO_CORREGIDO para observar logs mezclados y valores finales erráticos. Luego actívalo para ver cómo el contador se estabiliza y los mensajes se ordenan. Observa que el trabajo pesado queda fuera de la sección crítica en la versión segura.
Usa el ejemplo para practicar: agrega un tercer lock innecesario y mide el impacto, luego elimínalo para ver cómo mejora la contención.