Concurrencia en Rust: Mutex, RwLock, channels y Arc para estado compartido seguro

El modelo de ownership de Rust garantiza en tiempo de compilación que no haya data races: dos hilos no pueden leer y escribir el mismo dato simultáneamente sin sincronización. Pero escribir código concurrente correcto requiere entender las herramientas disponibles: Mutex para acceso exclusivo, RwLock para lecturas paralelas, canales mpsc para comunicación por mensajes y Barrier para sincronización por fases.

Arc<Mutex<T>>: estado mutable compartido

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let contador = Arc::new(Mutex::new(0u64));
    let mut handles = vec![];

    for _ in 0..8 {
        let c = Arc::clone(&contador);
        handles.push(thread::spawn(move || {
            for _ in 0..1000 {
                let mut guard = c.lock().unwrap();
                *guard += 1;
            } // guard se libera automáticamente al salir de scope
        }));
    }

    for h in handles { h.join().unwrap(); }
    println!("Total: {}", *contador.lock().unwrap()); // 8000
}

RwLock: múltiples lectores, un escritor

use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let cache = Arc::new(RwLock::new(vec!["inicial".to_string()]));
    let mut handles = vec![];

    // 5 hilos lectores en paralelo
    for i in 0..5 {
        let c = Arc::clone(&cache);
        handles.push(thread::spawn(move || {
            let r = c.read().unwrap(); // múltiples reads simultáneos OK
            println!("Lector {i}: {:?}", *r);
        }));
    }

    // 1 hilo escritor (espera a que no haya lectores)
    let c = Arc::clone(&cache);
    handles.push(thread::spawn(move || {
        let mut w = c.write().unwrap(); // exclusivo
        w.push("nuevo elemento".to_string());
    }));

    for h in handles { h.join().unwrap(); }
}

Canales mpsc: comunicación por mensajes

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main() {
    let (tx, rx) = mpsc::channel();

    // Múltiples productores
    for i in 0..3 {
        let tx_clon = tx.clone();
        thread::spawn(move || {
            thread::sleep(Duration::from_millis(i * 100));
            tx_clon.send(format!("mensaje de hilo {i}")).unwrap();
        });
    }
    drop(tx); // cerrar el transmisor original para que rx sepa cuándo acabar

    // El receptor bloquea hasta recibir o que todos los tx se cierren
    for msg in rx {
        println!("Recibido: {msg}");
    }
}

Canal síncrono con capacidad limitada

use std::sync::mpsc;
use std::thread;

fn main() {
    // sync_channel: el emisor se bloquea si el buffer está lleno
    let (tx, rx) = mpsc::sync_channel::(2); // buffer de 2

    let h = thread::spawn(move || {
        for i in 0..5 {
            println!("Enviando {i}");
            tx.send(i).unwrap(); // se bloquea cuando buffer lleno
        }
    });

    thread::sleep(std::time::Duration::from_millis(100));
    for _ in 0..5 {
        println!("Recibido: {}", rx.recv().unwrap());
    }
    h.join().unwrap();
}

Barrier: sincronización por fases

use std::sync::{Arc, Barrier};
use std::thread;

fn main() {
    let barrera = Arc::new(Barrier::new(4));
    let mut handles = vec![];

    for i in 0..4 {
        let b = Arc::clone(&barrera);
        handles.push(thread::spawn(move || {
            println!("Hilo {i}: fase 1");
            b.wait(); // todos esperan aquí hasta que 4 hilos lleguen

            println!("Hilo {i}: fase 2");
            b.wait();

            println!("Hilo {i}: terminado");
        }));
    }

    for h in handles { h.join().unwrap(); }
}

El error de compilación más frecuente

use std::rc::Rc;
use std::thread;

fn main() {
    let datos = Rc::new(vec![1, 2, 3]);
    let d = Rc::clone(&datos);

    thread::spawn(move || {
        println!("{:?}", d);
    });
}

// error[E0277]: `Rc<Vec<i32>>` cannot be sent between threads safely
// Solución: usa Arc en lugar de Rc

Resumen

  • Arc<Mutex<T>>: el patrón estándar para estado mutable compartido entre hilos.
  • RwLock: permite múltiples lectores simultáneos; un solo escritor con acceso exclusivo.
  • mpsc::channel(): canales de capacidad ilimitada; sync_channel para buffers acotados con back-pressure.
  • Clona el Sender para múltiples productores; cierra todos los transmisores para señalizar fin.
  • Barrier sincroniza fases: todos los hilos esperan hasta que el último llega.
  • Rc no implementa Send; usa Arc siempre que datos crucen límites de hilo.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP