Threads en Rust: crear hilos con spawn y esperar con join

Rust fue diseñado desde el principio para la concurrencia segura. El compilador detecta las carreras de datos en tiempo de compilación, no en runtime. El modelo básico de concurrencia usa thread::spawn para crear hilos del sistema operativo, move closures para transferir datos al hilo y JoinHandle::join para esperar su finalización.

Crear un hilo con spawn

use std::thread;
use std::time::Duration;

fn main() {
    let hilo = thread::spawn(|| {
        for i in 1..=5 {
            println!("Hilo secundario: {}", i);
            thread::sleep(Duration::from_millis(100));
        }
    });

    for i in 1..=3 {
        println!("Hilo principal: {}", i);
        thread::sleep(Duration::from_millis(150));
    }

    hilo.join().unwrap(); // espera a que termine el hilo secundario
}

JoinHandle: esperar y recoger el resultado

use std::thread;

fn main() {
    let handle: thread::JoinHandle<i32> = thread::spawn(|| {
        let suma: i32 = (1..=100).sum();
        suma // el closure retorna este valor
    });

    let resultado = handle.join().unwrap(); // recoge el i32
    println!("Suma 1..100 = {}", resultado); // 5050
}

join() bloquea el hilo actual hasta que el otro termina y retorna Result<T, Box<dyn Any>>: Ok(valor_retornado) si el hilo terminó correctamente, Err si paniqeó.

move closures: transferir datos al hilo

use std::thread;

fn main() {
    let datos = vec![1, 2, 3, 4, 5];

    // Sin move: el compilador no sabe si datos vivirá suficiente
    // let hilo = thread::spawn(|| println!("{:?}", datos)); // ERROR

    // Con move: datos se transfiere al hilo
    let hilo = thread::spawn(move || {
        println!("{:?}", datos); // datos pertenece al hilo
    });

    // println!("{:?}", datos); // ERROR: datos se movió
    hilo.join().unwrap();
}

El trait Send

Solo puedes mover al hilo tipos que implementen Send. La mayoría de tipos de la stdlib lo implementan. Rc<T> no lo implementa (usa Arc<T> para datos compartidos entre hilos).

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

fn main() {
    let rc = Rc::new(5);
    // thread::spawn(move || println!("{}", rc)); // ERROR: Rc no es Send
}

Múltiples hilos con resultados

use std::thread;

fn main() {
    let tareas: Vec<i32> = (1..=8).collect();

    let handles: Vec<_> = tareas.iter().map(|&n| {
        thread::spawn(move || {
            // Simula trabajo
            n * n
        })
    }).collect();

    let resultados: Vec<i32> = handles.into_iter()
        .map(|h| h.join().unwrap())
        .collect();

    println!("{:?}", resultados); // [1, 4, 9, 16, 25, 36, 49, 64]
}

available_parallelism: cuántos hilos tiene sentido crear

use std::thread;

fn main() {
    let nucleos = thread::available_parallelism()
        .map(|n| n.get())
        .unwrap_or(1);

    println!("CPUs disponibles: {}", nucleos);

    // Crear exactamente un hilo por CPU
    let handles: Vec<_> = (0..nucleos).map(|i| {
        thread::spawn(move || {
            println!("Hilo {} corriendo", i);
        })
    }).collect();

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

Resumen

  • thread::spawn(closure): crea un hilo del SO y retorna un JoinHandle.
  • handle.join(): espera al hilo y recoge su valor de retorno o el panic.
  • move en el closure: transfiere la propiedad de los datos al hilo.
  • Solo tipos Send pueden cruzar a otro hilo. Rc no es Send; usa Arc.
  • thread::available_parallelism(): número de CPUs disponibles.

El siguiente artículo cubre los canales (mpsc): la forma idiomática de comunicar hilos en Rust pasando mensajes en lugar de compartir estado.

COMPARTE ESTE ARTÍCULO

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