Rust: Concurrencia

Descubre cómo Rust garantiza concurrencia segura y paralelismo eficiente con threads, channels y programación asíncrona.

Aprende Rust GRATIS y certifícate

Concurrencia en Rust

La concurrencia representa uno de los aspectos más distintivos y revolucionarios de Rust como lenguaje de programación. Mientras que muchos lenguajes luchan con los problemas inherentes de la programación concurrente, Rust ofrece un enfoque único que combina rendimiento y seguridad sin comprometer ninguno de los dos aspectos.

El paradigma de concurrencia segura

Rust aborda la concurrencia desde una perspectiva fundamentalmente diferente. El sistema de ownership y el borrow checker trabajan conjuntamente para prevenir las condiciones de carrera y los accesos concurrentes no seguros en tiempo de compilación, eliminando una clase completa de errores que tradicionalmente han plagado la programación concurrente.

La filosofía de Rust se basa en el principio de "fearless concurrency" - concurrencia sin miedo. Esto significa que puedes escribir código concurrente con la confianza de que el compilador detectará y prevendrá los errores más comunes antes de que lleguen a producción.

Threads y el modelo de concurrencia

Los threads en Rust proporcionan paralelismo real, permitiendo que diferentes partes de tu programa se ejecuten simultáneamente en múltiples núcleos del procesador. El módulo std::thread ofrece las primitivas fundamentales para crear y gestionar threads de manera segura.

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("Thread secundario: {}", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("Thread principal: {}", i);
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().unwrap();
}

La función thread::spawn crea un nuevo thread y devuelve un JoinHandle, que permite esperar a que el thread termine su ejecución mediante el método join().

Comunicación entre threads

La comunicación entre threads es fundamental para construir aplicaciones concurrentes efectivas. Rust proporciona varios mecanismos para que los threads intercambien información de manera segura, siendo los channels uno de los más elegantes y seguros.

Los channels implementan el patrón "Do not communicate by sharing memory; instead, share memory by communicating", popularizado por Go. Este enfoque reduce significativamente la complejidad y los errores asociados con el estado compartido.

use std::sync::mpsc;
use std::thread;

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

    thread::spawn(move || {
        let mensaje = String::from("Hola desde el thread");
        tx.send(mensaje).unwrap();
    });

    let recibido = rx.recv().unwrap();
    println!("Mensaje recibido: {}", recibido);
}

Estado compartido y sincronización

Aunque los channels son preferibles, a veces necesitas estado compartido entre threads. Rust proporciona primitivas de sincronización como Mutex y Arc que permiten compartir datos de manera segura.

El tipo Arc<Mutex<T>> es un patrón común que combina reference counting atómico con exclusión mutua, permitiendo que múltiples threads accedan a los mismos datos de forma coordinada.

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

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

    for _ in 0..10 {
        let contador = Arc::clone(&contador);
        let handle = thread::spawn(move || {
            let mut num = contador.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

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

    println!("Resultado: {}", *contador.lock().unwrap());
}

Programación asíncrona

La programación asíncrona representa otro paradigma de concurrencia que Rust soporta de manera nativa. A través de las palabras clave async y await, puedes escribir código que maneja operaciones de E/S de manera eficiente sin bloquear threads.

Las futures son el corazón del modelo asíncrono de Rust, representando computaciones que pueden completarse en el futuro. Este modelo es especialmente efectivo para aplicaciones que manejan muchas operaciones de red o E/S concurrentes.

async fn operacion_asincrona() -> String {
    // Simula una operación asíncrona
    "Operación completada".to_string()
}

async fn main_asincrono() {
    let resultado = operacion_asincrona().await;
    println!("{}", resultado);
}

Patrones avanzados de concurrencia

Rust permite implementar patrones avanzados como el patrón actor, pools de threads, y sistemas de trabajo distribuido. La combinación de ownership, lifetimes y el sistema de tipos permite crear abstracciones concurrentes que son tanto eficientes como seguras.

El rayon crate es un ejemplo excelente de cómo Rust puede hacer que el paralelismo de datos sea simple y seguro, permitiendo convertir iteradores secuenciales en paralelos con cambios mínimos en el código.

La concurrencia en Rust no es solo sobre rendimiento; es sobre crear sistemas que sean predecibles, mantenibles y libres de errores relacionados con la concurrencia. Esta combinación única de características hace que Rust sea especialmente adecuado para sistemas que requieren tanto alta performance como alta confiabilidad.

Empezar curso de Rust

Lecciones de este módulo de Rust

Lecciones de programación del módulo Concurrencia del curso de Rust.