Concurrencia y multihilo en Rust: escribiendo código seguro y rápido
Los programas modernos rara vez funcionan en modo monohilo. El procesamiento de solicitudes, el trabajo con archivos, las operaciones de red — todo esto requiere la ejecución paralela de tareas. Rust ofrece un enfoque único para la concurrencia: garantiza la seguridad de memoria en tiempo de compilación, previniendo las condiciones de carrera. En este artículo analizaremos cómo trabajar con hilos, canales, mutex y asincronía en Rust.
1. Fundamentos del multihilo: creación y gestión de hilos
En Rust, los hilos se crean a través de la biblioteca estándar std::thread. Cada hilo se ejecuta de forma independiente, pero el compilador supervisa estrictamente el tiempo de vida de las variables.
Creación de un hilo
use std::thread;use std::time::Duration;
fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("Hilo: {}", i); thread::sleep(Duration::from_millis(100)); } });
for i in 1..5 { println!("Hilo principal: {}", i); thread::sleep(Duration::from_millis(100)); }
handle.join().unwrap(); // Esperamos a que el hilo termine}Captura de propiedad en closures
Si el hilo utiliza variables del ámbito externo, es necesario transferir la propiedad mediante move:
let data = vec![1, 2, 3];
let handle = thread::spawn(move || { println!("Datos: {:?}", data);});
handle.join().unwrap();// println!("{:?}", data); // ¡Error! data fue movida2. Intercambio de datos: mutex y bloqueos
Para el acceso seguro a datos compartidos desde múltiples hilos, Rust utiliza Mutex (mutex) en combinación con Arc (contador de referencias atómico).
Ejemplo con Mutex y Arc
use std::sync::{Arc, Mutex};use std::thread;
fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![];
for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); }
for handle in handles { handle.join().unwrap(); }
println!("Resultado: {}", *counter.lock().unwrap()); // 10}Importante: Mutex se desbloquea automáticamente al salir del ámbito de visibilidad. Esto previene el deadlock (bloqueo mutuo) a nivel de compilación.
RwLock — para lecturas frecuentes
Si los datos raramente se modifican pero se leen con frecuencia, use RwLock:
use std::sync::RwLock;
let lock = RwLock::new(5);
{ let r1 = lock.read().unwrap(); let r2 = lock.read().unwrap(); // Se puede leer simultáneamente println!("{} {}", r1, r2);}
{ let mut w = lock.write().unwrap(); *w += 1; // La escritura bloquea la lectura}3. Canales: transmisión de mensajes entre hilos
Rust soporta el modelo de actores a través de canales (std::sync::mpsc). Esto permite que los hilos intercambien datos sin memoria compartida.
Ejemplo con canal
use std::sync::mpsc;use std::thread;
fn main() { let (tx, rx) = mpsc::channel();
thread::spawn(move || { let vals = vec!["hello", "from", "the", "thread"]; for val in vals { tx.send(val).unwrap(); thread::sleep(std::time::Duration::from_secs(1)); } });
for received in rx { println!("Recibido: {}", received); }}Múltiples remitentes
Con mpsc::Sender se puede clonar el remitente para varios hilos:
let (tx, rx) = mpsc::channel();let tx1 = tx.clone();
thread::spawn(move || { tx1.send("Hilo 1").unwrap(); });thread::spawn(move || { tx.send("```