La sincronización en Rust permite coordinar la ejecución de hilos para evitar condiciones de carrera y asegurar que el flujo de datos sea consistente. Mientras que los Mutex protegen los datos, Condvar (Condition Variables) y Barrier gestionan el tiempo y el orden de ejecución.
Una Condvar permite que un hilo se duerma de forma eficiente hasta que se cumpla una condición lógica específica. No es suficiente con usar un Mutex; si un hilo solo intentara verificar un booleano en un bucle infinito, consumiría ciclos de CPU innecesariamente (busy-waiting). La Condvar soluciona esto permitiendo que el hilo se bloquee y libere el Mutex simultáneamente, permitiendo que otros hilos modifiquen el estado para finalmente despertarlo mediante notify_one() o notify_all(). Es crítico entender los spurious wakeups (despertas espurios): un hilo puede despertar de una Condvar sin que nadie haya enviado una señal. Por ello, la condición debe verificarse siempre dentro de un bucle while.
Por otro lado, una Barrier (Barrera) se utiliza para sincronizar un grupo de hilos en un punto de encuentro. Si creas una barrera con Barrier::new(n), ningún hilo podrá pasar de la llamada a barrier.wait() hasta que exactamente n hilos hayan llegado a ese punto. Esto es fundamental en algoritmos de computación paralela por fases, donde todos los hilos deben completar la fase de preparación antes de que cualquiera pueda iniciar la fase de ejecución.
El error más común al usar Condvar es olvidar que wait() requiere que el hilo posea el MutexGuard. Si no se vuelve a verificar la condición tras el despertar, el programa puede proceder con datos incompletos debido a los despertares espurios o a condiciones de carrera si otro hilo cambió el estado justo antes de la señal.
use std::sync::{Arc, Mutex, Condvar, Barrier};
use std::thread;
use std::time::Duration;
fn main() {
// Una barrera para que 3 hilos comiencen su tarea al mismo tiempo
let barrier = Arc::new(Barrier::new(3));
// Un par de Mutex y Condvar para coordinar una fase posterior
// El Mutex protege el booleano que indica si el trabajo terminó
let pair = Arc::new((Mutex::new(false), Condvar::new()));
let mut handles = vec![];
for i in 0..3 {
let b = Arc::clone(&barrier);
let p = Arc::clone(&pair);
let handle = thread::spawn(move || {
// 1. Sincronización por barrera
println!("Hilo {}: Esperando en la barrera...", i);
b.wait();
println!("Hilo {}: ¡Barrera cruzada!", i);
let (lock, cvar) = &*p;
if i == 0 {
// El hilo 0 realiza un trabajo pesado y luego avisa
thread::sleep(Duration::from_millis(500));
let mut started = lock.lock().unwrap();
*started = true;
println!("Hilo 0: Trabajo completado, notificando...");
cvar.notify_all(); // Despierta a todos los que esperen
} else {
// Los hilos 1 y 2 esperan a que el hilo 0 termine
let mut started = lock.lock().unwrap();
// El bucle 'while' es vital para evitar spurious wakeups
while !*started {
println!("Hilo {}: Condición no cumplida, durmiendo...", i);
started = cvar.wait(started).unwrap();
}
println!("Hilo {}: Condición recibida, continuando...", i);
}
});
handles.push(handle);
}
for h in handles {
h.join().unwrap();
}
println!("Proceso finalizado con éxito.");
}
Análisis del código
Barrier::new(3): Inicializa una barrera que bloquea a cualquier hilo hasta que 3 hilos llamen await().Arc::new((Mutex::new(false), Condvar::new())): Crea un par de estructuras compartidas.Arcpermite la propiedad múltiple entre hilos, elMutex<bool>protege la bandera de estado, y elCondvargestiona la suspensión.b.wait(): Cada hilo llega a este punto. El primer hilo en llegar se bloquea, el segundo espera, y cuando llega el tercero, la barrera se libera y todos continúan.let (lock, cvar) = &*p;: Desestructura elArcpara obtener referencias alMutexy a laCondvar.if i == 0 { ... }: El hilo 0 actúa como el productor de la señal.*started = true;: Modifica el valor protegido por elMutexpara cambiar la condición lógica.cvar.notify_all(): Envía una señal a todos los hilos que estén bloqueados en laCondvarasociada.while !*started { ... }: En los hilos 1 y 2, este bucle es la defensa contra los spurious wakeups. Si el hilo despierta pero*startedsigue siendofalse(porque la señal fue errónea o prematura), vuelve a entrar enwait.started = cvar.wait(started).unwrap();: Esta es la operación más crítica. El métodowaithace tres cosas atómicamente: libera elMutexGuard(started), bloquea el hilo actual y, tras ser notificado, vuelve a adquirir elMutexdevolviendo un nuevoMutexGuard.h.join().unwrap(): El hilo principal espera a que todos los hilos hijos terminen su ejecución antes de finalizar el programa.
N° 83