Bienvenidos sean a este post, hoy veremos una condicion rara que se da con los manejos de memoria.
Hasta ahora hablamos sobre la memoria, como se compone, como es utilizada, y tambien como podemos usar apuntadores de distintos objetos para obtener la informacion pero cuando varios apuntadores que trabajan al mismo tiempo sobre una direccion de memoria se genera una rara condicion llamada race condition, esto es debido a que multiples threads pueden estar corriendo al mismo tiempo y modificar la informacion pero nunca sabremos cual es el orden correcto porque nunca sabremos cual de los threads toma primero el valor, para entender el concepto vamos a ver un ejemplo.
Para ello crearemos un nuevo proyecto que llamaremos carrera, una vez generado modificaremos el codigo creado en main.rs con el siguiente:
main.rs
use std::thread;
use std::rc::Rc;
struct MiContador
{
contar: i32
}
fn no_funca()
{
let mut contador = Rc::new(MiContador{contar: 0});
thread::spawn(move ||
{
contador.contar += 1;
});
println!("{}", contador.contar);
}
fn main() {
no_funca()
}
Primero importaremos la libreria para crear y manejar los threads, luego importaremos una que nos permite trabajar con un apuntador de conteo de referencia de un solo sub proceso, despues crearemos una clase o struct para contar llamado MiContador, despues tenemos la funcion que no funcionara, por eso se llama no_funca ¯\_(ツ)_/¯, aqui primero crearemos un objeto llamado contador que sera el apuntador para contar y pasaremos a la clase anterior, luego por medio de spawn lo moveremos o incrementaremos la variable contar del objeto anterior por ultimo lo mostraremos en pantalla, para finalmente en el main llamaremos a esta funcion, compilemos y veamos que sucede:
tinchicus@dbn001vrt:~/lenguajes/rust/carrera$ cargo build
Compiling carrera v0.1.0 (/home/tinchicus/lenguajes/rust/carrera)
error[E0277]: `Rc<MiContador>` cannot be sent between threads safely
--> src/main.rs:12:2
|
12 | thread::spawn(move ||
| _____^^^^^^^^^^^^^_-
| | |
| | `Rc<MiContador>` cannot be sent between threads safely
13 | | {
14 | | contador.contar += 1;
15 | | });
| |_____- within this `[closure@src/main.rs:12:16: 15:3]`
|
= help: within `[closure@src/main.rs:12:16: 15:3]`, the trait `Send` is not implemented for `Rc<MiContador>`
= note: required because it appears within the type `[closure@src/main.rs:12:16: 15:3]`
note: required by a bound in `spawn`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `carrera` due to previous error
tinchicus@dbn001vrt:~/lenguajes/rust/carrera$
Si observan el compilador al ver que se puede generar una race condition al momento de usar el spawn, no solo evita la compilacion sino que tambien nos advierte que no hay una forma segura de poder enviar los datos entre los threads, si bien esto fue adrede esta situacion puede ocurrir pero como podemos solucionarlo? Tomemos el codigo y modifiquemoslo de la siguiente manera:
main.rs
use std::thread;
use std::sync::{Arc,Mutex};
struct MiContador
{
contar: i32
}
fn no_funca()
{
let contador = Arc::new(Mutex::new(MiContador{contar: 0}));
let otro_contador = contador.clone();
thread::spawn(move ||
{
let mut contador = otro_contador.lock()
.expect("Fallo el bloqueo del otro contador");
contador.contar += 1;
});
println!("{}", contador.lock().unwrap().contar);
}
fn main() {
no_funca()
}
Analicemos las modificaciones, en este caso dejamos de usar a la libreria Rc para utilizar a la libreria sync y de ella usaremos dos clase (Arc y Mutex), seguimos con la misma clase para contar, veamos a la nueva funcion no_funca, la primera modificacion sera en la variable contador que sera de tipo Arc, este es un apuntador de conteo de referencia de thread seguro el cual nos permitira manejar de forma los threads que manejan los apuntadores, dentro de este crearemos un objeto Mutex que nos permitira esperar hasta que los threads se vuelvan disponibles, y dentro de este crearemos un nuevo objeto de la clase MiContador tal como antes, por ultimo clonamos al objeto anterior en otro objeto por medio del metodo clone.
Despues volvemos a usar spawn con el metodo move pero ahora creamos una nueva variabe donde le diremos que bloquee al objeto clonado y si falla por medio de expect notificaremos que fallo, luego incrementaremos al objeto que creamos con Arc y Mutex, por ultimo lo mostramos pero ahora le agregamos un lock para bloquearlo y al unwrap por si falla y por ultimo la variable contar, si lo compilamos veremos lo siguiente:
tinchicus@dbn001vrt:~/lenguajes/rust/carrera$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/carrera`
0
tinchicus@dbn001vrt:~/lenguajes/rust/carrera$
Como pueden ver ahora se soluciono, por lo menos la parte del compilador, dado que ahora si tenemos las transacciones de los threads sobre los apuntadores y el compilador sabe quien va a trabajar en cada momento pero vamos a hablar un poco sobre estos dos temas.
Primero, el mutex es la abreviatura de exclusion mutua y es una forma de trabajar muy habitual en los lenguajes dado que nos permite bloquear (lock) un thread hasta que el otro termine y una vez finalizado lo libera para que pueda trabajar sin que se solapen, esto es muy util pero tambien tiene un gran riesgo como es el deadlock, es decir que dos threads se queden esperando que liberen sus respectivos bloqueos y ninguno de los dos se ejecute, por lo tanto si bien nos beneficia para poder organizar el orden de los threads tambien puede bloquearlos y dejarlos inservibles.
El otro llamado Arc, como dijimos tambien es un apuntador de conteo referenciado pero en este caso de thread seguro, lo cual junto a Mutex nos permiten tener de forma segura que cada vez que trabajemos sobre el apuntador la informacion sea destruida una vez que fue procesado, y recuerden que siempre debe trabajar en conjunto con Mutex tal como vimos en el ejemplo.
En resumen, hoy hemos visto una condicion rara llamada race condition, vimos porque se genera, cuales son los inconvenientes que nos puede generar, hemos visto un ejemplo y como el compilador nos advierte de esta situacion, despues hemos visto como solucionarlo y algunas librerias/metodologias que aplicamos en la solucion, espero les haya sido de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donación
Es para mantenimento del sitio, gracias!
$1.50
