Concurrencia y Multihilo en Rust: de la A a la Z
Las aplicaciones modernas requieren cada vez más un uso eficiente de los procesadores multinúcleo. Rust ofrece un enfoque único para la concurrencia y el multihilo, combinando alto rendimiento con garantías de seguridad de memoria en tiempo de compilación. En este artículo, analizaremos los conceptos clave: desde la creación de hilos hasta el uso de mutexes y canales, con ejemplos prácticos de código.
1. Fundamentos del multihilo: creación y gestión de hilos
En Rust, los hilos se crean con la biblioteca estándar std::thread. Cada hilo se ejecuta de forma independiente, pero puede intercambiar datos con otros hilos.
Ejemplo simple de 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: número {}", i); thread::sleep(Duration::from_millis(1)); } });
for i in 1..5 { println!("Hilo principal: número {}", i); thread::sleep(Duration::from_millis(1)); }
handle.join().unwrap(); // Esperamos a que termine el hilo}Puntos clave:
thread::spawnacepta un closure que se ejecuta en un nuevo hilo.- El método
joinbloquea el hilo actual hasta que el hilo especificado finalice. - Sin
join, el hilo principal podría finalizar antes que el hilo secundario.
Para transferir datos entre hilos, use closures con move:
use std::thread;
fn main() { let data = vec![1, 2, 3, 4, 5];
let handle = thread::spawn(move || { println!("Datos desde el hilo: {:?}", data); });
handle.join().unwrap(); // println!("{:?}", data); // ¡Error! data ya fue movido}La palabra clave move transfiere la propiedad de los datos al closure, evitando condiciones de carrera en tiempo de compilación.
2. Intercambio seguro de datos: mutexes y bloqueos
Cuando varios hilos acceden a datos compartidos, existe el riesgo de condiciones de carrera. Rust resuelve este problema con mutexes (Mutex) y tipos atómicos.
Ejemplo de uso de Mutex con Arc (contador de referencias atómico):
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}Detalles importantes:
Arc<T>permite que varios hilos posean los mismos datos (clonación atómica).Mutex::lock()devuelve un bloqueo; si el bloqueo ya está ocupado, el hilo espera.- El bloqueo se libera automáticamente al salir del ámbito.
Para un control más preciso, use RwLock, que separa lectores y escritores:
use std::sync::RwLock;
let lock = RwLock::new(5);
// Lectura: varios hilos simultáneamente{ let r1 = lock.read().unwrap(); let r2 = lock.read().unwrap(); println!("Leyendo: {} y {}", r1, r2);}
// Escritura: solo un hilo{ let mut w = lock.write().unwrap(); *w += 1; println!("Escribiendo: {}", w);}3. Canales para transmisión de mensajes
Una alternativa a la memoria compartida es el modelo de actores mediante canales. Rust proporciona un canal multiproductor y un solo consumidor (mpsc).
Ejemplo básico:
u