En Rust, el sistema de préstamos (borrow checker) impide que existan múltiples referencias mutables a un mismo dato simultáneamente para evitar condiciones de carrera (race conditions). Sin embargo, en entornos multihilo, necesitamos compartir datos y modificarlos desde diferentes hilos. Aquí es donde entra la mutabilidad interior segura, permitiéndonos modificar datos incluso cuando estos están detrás de una referencia inmutable, siempre que el acceso esté sincronizado.

Mutex<T> (Mutual Exclusion) garantiza que un solo hilo pueda acceder al dato T a la vez. Para obtener acceso, un hilo debe llamar al método .lock(), el cual bloquea el hilo hasta que el recurso esté disponible. Si un hilo entra en panic! mientras posee el bloqueo, el Mutex se marca como “envenenado” (poisoned); esto es una medida de seguridad, ya que el dato podría haber quedado en un estado inconsistente. En ese caso, .lock() devolverá un Err, obligándonos a decidir si recuperamos el valor con .unwrap() o manejamos el error. El acceso se gestiona mediante un MutexGuard, que implementa el patrón RAII: el bloqueo se libera automáticamente en cuanto la variable sale de su ámbito (se ejecuta su drop).

RwLock<T> (Read-Write Lock) es una alternativa optimizada para escenarios donde las lecturas son mucho más frecuentes que las escrituras. A diferencia del Mutex, que permite un solo acceso (lectura o escritura), el RwLock permite que múltiples hilos lean el contenido de forma simultánea, pero solo un hilo puede escribir a la vez, bloqueando incluso a los lectores. Aunque ofrece mayor concurrencia, tiene un overhead computacional ligeramente superior al Mutex y puede sufrir de “inanición” (starvation) si un flujo constante de lectores impide que un escritor obtenga el acceso.

Para compartir estos contenedores entre hilos, es imprescindible envolver el Mutex o el RwLock en un Arc<T> (Atomic Reference Counted), lo que permite que múltiples hilos posean una propiedad compartida del objeto de sincronización.

use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use std::time::Duration;

fn main() {
    // Usamos Arc para compartir la propiedad entre hilos
    // Mutex para un contador que cambiará frecuentemente
    let contador = Arc::new(Mutex::new(0));
    
    // RwLock para una configuración que se lee mucho pero cambia poco
    let config = Arc::new(RwLock::new("Configuración Inicial".to_string()));

    let mut handles = vec![];

    // --- Bloque de Mutex: Incremento de contador ---
    for i in 0..3 {
        let c_ref = Arc::clone(&contador);
        let handle = thread::spawn(move || {
            // .lock() devuelve un MutexGuard que permite mutación mediante dereferenciación
            let mut num = c_ref.lock().unwrap();
            *num += 1;
            println!("Hilo de incremento {}: Contador ahora es {}", i, *num);
            // El bloqueo se libera automáticamente aquí al caer 'num' de ámbito
        });
        handles.push(handle);
    }

    // --- Bloque de RwLock: Lectores simultáneos ---
    for i in 0..2 {
        let r_ref = Arc::clone(&config);
        let handle = thread::spawn(move || {
            // .read() permite múltiples hilos a la vez
            let lectura = r_ref.read().unwrap();
            println!("Hilo lector {}: La config es '{}'", i, *lectura);
        });
        handles.push(handle);
    }

    // --- Bloque de RwLock: Escritor ---
    let w_ref = Arc::clone(&config);
    let writer_handle = thread::spawn(move || {
        // Espera a que no haya lectores ni otros escritores
        let mut escritura = w_ref.write().unwrap();
        *escritura = "Configuración Actualizada".to_string();
        println!("Hilo escritor: Configuración cambiada!");
    });
    handles.push(writer_handle);

    // Esperar a que todos los hilos terminen
    for handle in handles {
        handle.join().unwrap();
    }

    println!("---");
    println!("Resultado final - Contador: {}", *contador.lock().unwrap());
    println!("Resultado final - Config: {}", *config.read().unwrap());
}

Análisis del código

  1. Arc::clone(&contador): Creamos una nueva referencia atómica del puntero inteligente para que cada hilo sea dueño de su propia copia del puntero, pero todos apunten a la misma dirección de memoria.
  2. c_ref.lock().unwrap(): El hilo intenta adquirir el control del Mutex. El método devuelve un MutexGuard<i32>. Usamos .unwrap() para propagar el error si el mutex está poisoned. Mediante el operador de desreferencia *num, accedemos al valor interno para incrementarlo.
  3. r_ref.read().unwrap(): En los hilos lectores, obtenemos un RwLockReadGuard<String>. Este permiso es compartido; otros hilos podrían llamar a .read() al mismo tiempo sin bloquearse.
  4. w_ref.write().unwrap(): El hilo escritor solicita un RwLockWriteGuard<String>. Este método es exclusivo: si hay hilos leyendo o escribiendo, este hilo se bloqueará hasta que los demás terminen.
  5. RAII (Resource Acquisition Is Initialization): No llamamos explícitamente a una función unlock(). La liberación de los bloqueos ocurre automáticamente cuando las variables num, lectura y escritura salen de su respectivo bloque de código, garantizando que no haya bloqueos perpetuos por errores de lógica.

57