Arc<T> es la versión segura para hilos (thread-safe) del puntero inteligente Rc<T>. Mientras que Rc<T> (Reference Counted) gestiona la propiedad compartida en un solo hilo utilizando un contador de referencias simple, Arc<T> (Atomically Reference Counted) utiliza operaciones atómicas para actualizar dicho contador. Esto es indispensable en entornos multihilo, ya que las operaciones atómicas garantizan que el incremento y decremento del contador de referencias sea una operación indivisible, evitando condiciones de carrera cuando varios hilos intentan gestionar la vida útil del mismo objeto simultáneamente.

Para que Arc<T> sea útil en un contexto de concurrencia, el tipo de dato contenido T debe implementar los traits Send y Sync. Es crucial entender que Arc<T> proporciona propiedad compartida, pero es inmutable por defecto. Si se necesita modificar el valor contenido dentro de un Arc desde múltiples hilos, se debe emplear el patrón de mutabilidad compartida, envolviendo el dato en un Mutex<T> o un RwLock<T>. Por lo tanto, el patrón más común en Rust para estado compartido mutable es Arc<Mutex<T>>. Aunque Arc es extremadamente potente, tiene un coste de rendimiento superior a Rc debido a la sobrecarga de las instrucciones atómicas de la CPU; por ello, solo debe utilizarse cuando la propiedad deba cruzar los límites de un hilo.

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

fn main() {
    // Creamos un valor compartido envuelto en un Mutex para permitir mutabilidad
    // y en un Arc para permitir la propiedad compartida entre hilos.
    let contador = Arc::new(Mutex::new(0));
    
    // Vector para almacenar los handles de los hilos y poder esperar su finalización.
    let mut handles = vec![];

    for _ in 0..10 {
        // Clonamos el Arc para aumentar el contador de referencias de forma atómica.
        // Esto permite que cada hilo tenga su propio "puntero" al mismo Mutex.
        let contador_clon = Arc::clone(&contador);

        let handle = thread::spawn(move || {
            // Bloqueamos el Mutex para obtener el acceso exclusivo al dato interno.
            // .lock() devuelve un MutexGuard que permite la mutación.
            let mut num = contador_clon.lock().unwrap();
            *num += 1;
            // El Mutex se libera automáticamente cuando 'num' sale de scope.
        });
        
        handles.push(handle);
    }

    // Esperamos a que todos los hilos terminen su ejecución.
    for handle in handles {
        handle.join().unwrap();
    }

    // Imprimimos el resultado final, bloqueando una última vez para leer el valor.
    println!("Resultado final del contador: {}", *contador.lock().unwrap());
}

Explicación del Código

  • let contador = Arc::new(Mutex::new(0));: Se inicializa un Arc que contiene un Mutex protegiendo un entero. Arc gestiona la vida útil compartida y Mutex garantiza la mutabilidad segura.
  • let contador_clon = Arc::clone(&contador);: Se utiliza Arc::clone para crear una nueva referencia al mismo objeto. Esto incrementa el contador de referencias atómicamente, permitiendo que el hilo hijo sea el dueño de su propia copia del puntero.
  • thread::spawn(move || { ... }): La palabra clave move transfiere la propiedad de contador_clon al closure que se ejecutará en el nuevo hilo.
  • let mut num = contador_clon.lock().unwrap();: El método lock() bloquea el hilo hasta que el Mutex esté disponible. El tipo de num es MutexGuard<i32>, que permite modificar el entero mediante desreferenciación (*num += 1).
  • handle.join().unwrap();: Se utiliza para asegurar que el hilo principal no termine hasta que todos los hilos secundarios hayan completado su tarea, garantizando que el resultado final sea el esperado (10).

55