Adrianistán

El blog de Adrián Arroyo


Concurrencia en Rust

- Adrián Arroyo Calle

Rust destaca por su soporte nativo a la concurrencia. Aquí veremos exactamente qué es lo que hace.

Crear threads


Todas las funciones relacionadas con threads están en std::thread así que primero hemos de importarlo con use. Para crear un thread usamos thread::spawn, que toma una función como argumento. Esta operación devuelve un JoinHandler que puede ser usado para esperar a que finalicen los hilos de ejecución.


use std::thread;

fn main(){
let handle = thread::spawn(||{
for i in 1..100{
println!("Attens ou va t'en - France Gall");
}
});
handle.join();
println!("Hilo finalizado");
}


Aquí ya se presenta una cuestión interesante, ¿qué pasa si queremos llevar datos del hilo principal al nuevo thread? Si lo intentamos hacer, Rust se quejará. En realidad, tenemos que activar la closure con move. Lo que hace es transferir la propiedad del dato a la closure. Con eso podemos procesarlo tranquilamente en el hilo nuevo, siempre y cuando no intentemos acceder a él desde el hilo principal.


use std::thread;

fn main(){
let v = vec!["Attens ou va t'en - Paul Mauriat","Attens ou va t'en - France Gall"];
let handle = thread::spawn(move ||{
for i in v{
println!("{}",i);
}
});
handle.join();
println!("Hilo finalizado");
}


Mensajes


Una de las opciones que permite Rust para concurrencia es el paso de mensajes.


use std::thread;
use std::sync::mpsc;
use std::time::Duration;


fn main(){
let (tx,rx) = mpsc::channel();

let h = thread::spawn(move ||{
thread::sleep(Duration::new(5,0));
let val = String::from("Attens ou va t'en - France Gall");
tx.send(val).unwrap();
});

loop {
if let Ok(msg) = rx.try_recv() {
println!("{}",msg);
break;
}
}
// Opción síncrona
//let msg = rx.recv().unwrap();
//println!("{}",msg);

// Opción Iterable
// for msg in rx{
// println!("{}",msg);
// }
}


Tx es el objeto usado para transmitir y Rx para recibir. Tx lo puede tener cualquier hilo para mandar mensajes mientras que Rx está limitado al hilo principal. Existen varias formas de recibir los mensajes. try_recv no bloquea el hilo de ejecución, por lo que puede usarse en un bucle con if-let. recv es síncrono y bloquea el hilo de ejecución si hace falta. Tx también es Iterable así que podemos leer los mensajes en un bucle for-in.

Mutex y Arc


Cuando queremos compartir memoria en Rust tenemos que recurrir a una combinación de Mutex y Arc.

Mutex provee de gestión del acceso a memoria. Para acceder al dato es necesario hacer lock. No es estrictamente necesario hacer unlock una vez hayamos modificado el dato, pues cuando Rust libere memoria, también hará unlock. Sin embargo, Mutex por sí solo no es suficiente.

Arc (Atomic Reference Counter) permite tener datos con múltiples dueños. Esto funciona con una especie de recolector de basura y es útil porque cuando hablamos de hilos podría darse la situación de que el hilo dueño muriese y un hilo con los datos prestados todavía siguiese vivo. Con clone conseguimos una copia para mover libremente que en realidad referencia al mismo dato en memoria.


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

fn main(){
let counter = Arc::new(Mutex::new(42));
let mut handles = vec![];

for _ in 0..10 {
let counter = counter.clone();
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();

*num += 1;
});
handles.push(handle);
}

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

println!("Result: {}", *counter.lock().unwrap());
}


Con esto ya tendremos lo suficiente para manejar concurrencia en Rust. Por supuesto, esto es más complejo que esto y si de verdad quieres aprovechar el potencial al máximo quizá debas revisar la documentación de las traits Send y Sync.

 

 
Alejandro :3
Buena información sobre la concurrencia en Rust, ejemplos bastante claros y una sintaxis clara. Creo que hace falta mas la aplicaron de estas tecnologías por parte de los desarrolladores.

Añadir comentario

Todos los comentarios están sujetos a moderación