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 unJoinHandle.handle.join(): espera al hilo y recoge su valor de retorno o el panic.moveen el closure: transfiere la propiedad de los datos al hilo.- Solo tipos
Sendpueden cruzar a otro hilo.Rcno esSend; usaArc. 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.
