parking_lot Rust: guía completa con ejemplos prácticos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

parking_lot para Rust: primitivas de sincronización rápidas

La biblioteca parking_lot es una de las implementaciones más populares y eficientes de primitivas de sincronización para el lenguaje Rust. Ofrece una alternativa a los mutex estándar (std::sync::Mutex), bloqueos de lectura-escritura (std::sync::RwLock) y variables condicionales (std::sync::Condvar), pero con un rendimiento significativamente superior y un código más compacto.

¿Por qué usar esta biblioteca? Las primitivas estándar de Rust, aunque seguras, tienen varias desventajas: utilizan llamadas al sistema (futex en Linux) incluso en escenarios simples, requieren memoria adicional para banderas y tienen una implementación interna más compleja. parking_lot resuelve estos problemas empleando algoritmos más eficientes (como bloqueos optimistas y bucles de espera activa) y minimizando la sobrecarga. Como resultado, el código se ejecuta más rápido, especialmente en aplicaciones multihilo con alta contención.

La biblioteca es totalmente compatible con Rust y no requiere código unsafe por parte del usuario. Ofrece las mismas interfaces que la biblioteca estándar, pero con funcionalidades adicionales: Mutex con soporte de timeouts, RwLock con cola justa, Once para inicialización diferida y ReentrantMutex para bloqueos reentrantes. parking_lot es el estándar de facto para aplicaciones Rust de alto rendimiento, desde servidores web hasta motores de juegos.

Instalación

Añade la biblioteca al archivo Cargo.toml de tu proyecto:

[dependencies]
parking_lot = "0.12"

Luego ejecuta el comando:

cargo build

O, si usas cargo add (desde Rust 1.62):

cargo add parking_lot

Después de esto, puedes importar las primitivas desde la biblioteca:

use parking_lot::{Mutex, RwLock, Condvar, Once, ReentrantMutex};

Inicio rápido: ejemplo mínimo funcional

Aquí tienes un ejemplo sencillo de uso de Mutex de parking_lot para proteger un contador compartido en un entorno multihilo:

use parking_lot::Mutex;
use std::thread;

fn main() {
    // Creamos un mutex que envuelve el valor
    let counter = Mutex::new(0);

    // Creamos 10 hilos, cada uno incrementa el contador en 1
    let mut handles = vec![];
    for _ in 0..10 {
        let handle = thread::spawn(|| {
            // Adquirimos el bloqueo — .lock() devuelve un guard RAII
            let mut guard = counter.lock();
            *guard += 1;
            // guard se libera automáticamente al salir del ámbito
        });
        handles.push(handle);
    }

    // Esperamos a que todos los hilos terminen
    for handle in handles {
        handle.join().unwrap();
    }

    // Leemos el valor final
    let final_value = *counter.lock();
    println!("Valor final: {}", final_value); // Debería ser 10
}

En este ejemplo, counter.lock() devuelve un MutexGuard que libera automáticamente el bloqueo al destruirse. A diferencia del std::sync::Mutex estándar, aquí no es necesario manejar errores de envenenamiento (poisoning) — parking_lot no envenena el mutex por defecto cuando ocurre un pánico.

Métodos y tipos principales

A continuación se presentan 8 métodos/tipos clave de la biblioteca parking_lot con sus firmas, descripciones y ejemplos.

1. Mutex::lock()

Firma: pub fn lock(&self) -> MutexGuard<'_, T>

Descripción: Adquiere el mutex, bloqueando el hilo actual hasta que se obtenga el bloqueo. Devuelve un guard RAII que libera automáticamente el bloqueo al salir del ámbito.

use parking_lot::Mutex;

let data = Mutex::new(42);
{
    let guard = data.lock();
    println!("Valor: {}", *guard);
} // El bloqueo se libera aquí

2. Mutex::try_lock()

Firma: pub fn try_lock(&self) -> Option<MutexGuard<'_, T>>

Descripción: Intenta adquirir el mutex sin bloquearse. Devuelve Some(guard) si tiene éxito, o None si el mutex ya está bloqueado.

use parking_lot::Mutex;

let data = Mutex::new(10);
if let Some(mut guard) = data.try_lock() {
    *guard += 5;
    println!("Bloqueo adquirido, nuevo valor: {}", *guard);
} else {
    println!("No se pudo adquirir el bloqueo");
}

3. RwLock::read()

Firma: pub fn read(&self) -> RwLockReadGuard<'_, T>

Descripción: Adquiere un bloqueo de lectura. Múltiples lecturas pueden ocurrir simultáneamente, pero ningún escritor puede acceder mientras haya lectores activos.

use parking_lot::RwLock;

let config = RwLock::new(String::from("config.json"));
let reader = config.read();
println!("Configuración: {}", *reader);
// reader se libera aquí

4. RwLock::write()

Firma: pub fn write(&self) -> RwLockWriteGuard<'_, T>

Descripción: Adquiere un bloqueo de escritura exclusivo. Solo un escritor puede acceder, y ningún lector puede hacerlo mientras el escritor esté activo.

use parking_lot::RwLock;

let data = RwLock::new(vec![1, 2, 3]);
{
    let mut writer = data.write();
    writer.push(4);
    println!("Datos actualizados: {:?}", *writer);
} // El bloqueo de escritura se libera aquí

5. Condvar::wait()

Firma: pub fn wait<T>(&self, mutex_guard: &mut MutexGuard<'_, T>)

Descripción: Bloquea el hilo actual hasta que la variable condicional sea notificada. Libera el mutex mientras espera y lo readquiere antes de regresar.

use parking_lot::{Mutex, Condvar};

let pair = (Mutex::new(false), Condvar::new());
let (lock, cvar) = &pair;

let mut guard = lock.lock();
while !*guard {
    cvar.wait(&mut guard);
}
println!("¡Notificación recibida!");

6. Condvar::notify_one()

Firma: pub fn notify_one(&self)

Descripción: Despierta un hilo que esté esperando en esta variable condicional. Si no hay hilos esperando, no hace nada.

use parking_lot::{Mutex, Condvar};
use std::thread;

let pair = (Mutex::new(false), Condvar::new());
let (lock, cvar) = &pair;

thread::spawn(move || {
    let mut guard = lock.lock();
    *guard = true;
    cvar.notify_one();
});

let mut guard = lock.lock();
while !*guard {
    cvar.wait(&mut guard);
}
println!("Hilo principal despertado");

7. Once::call_once()

Firma: pub fn call_once<F>(&self, f: F) where F: FnOnce()

Descripción: Ejecuta una función de inicialización exactamente una vez. Si se llama múltiples veces desde múltiples hilos, solo una ejecución ocurre.

use parking_lot::Once;

static INIT: Once = Once::new();
static mut DATA: Vec = Vec::new();

fn init_data() {
    unsafe {
        DATA.push(1);
        DATA.push(2);
        DATA.push(3);
    }
}

fn main() {
    INIT.call_once(|| {
        init_data();
    });
    println!("Inicialización completada");
}

8. ReentrantMutex::lock()

Firma: pub fn lock(&self) -> ReentrantMutexGuard<'_, T>

Descripción: Adquiere un mutex reentrante. El mismo hilo puede adquirir el bloqueo múltiples veces sin bloquearse, pero debe liberarlo el mismo número de veces.

use parking_lot::ReentrantMutex;

let mutex = ReentrantMutex::new(0);
let guard1 = mutex.lock();
let guard2 = mutex.lock(); // El mismo hilo puede re-adquirir
println!("Valor: {}", *guard1);
// Ambos guards se liberan al salir del ámbito

Conclusión

parking_lot es una biblioteca esencial para cualquier desarrollador Rust que busque maximizar el rendimiento en aplicaciones concurrentes. Sus primitivas de sincronización son más rápidas, ligeras y flexibles que las de la biblioteca estándar, manteniendo la misma seguridad de tipos y ausencia de código unsafe. Ya sea que estés construyendo un servidor web, un motor de juegos o una herramienta de procesamiento de datos, parking_lot te ayudará a escribir código concurrente más eficiente y limpio.

Recommendations