Historia del problema:
El trabajo con multihilo es fuente de errores en la mayoría de los lenguajes de programación: condiciones de carrera, competencia por recursos, errores no obvios. Estudiando la experiencia de C++ y Java, los creadores de Rust decidieron incorporar mecanismos de seguridad de hilos directamente en el sistema de tipos, para que la mayoría de los errores se detecten ya en tiempo de compilación.
Problema:
En los lenguajes clásicos, a menudo hay que depender de la disciplina del programador y herramientas externas: los riesgos de transmisión de la propiedad de los datos, la memoria compartida mutable y la falta de control sobre el acceso concurrente pueden conducir a fallos críticos. Era necesario proporcionar un sistema que garantizara la ausencia de condiciones de carrera en la etapa de compilación.
Solución:
En Rust, para la sincronización y transmisión de datos entre hilos se utilizan tipos especiales de la biblioteca estándar, como Arc, Mutex y canales. Los marcadores de rasgos Send y Sync juegan un papel crucial, que son verificados automáticamente por el compilador. Un tipo se considera thread-safe si:
Sync)SendEjemplo de código:
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()); }
Características clave:
Mutex, RwLock, canales son thread-safe por contratoArc, Mutex), no a través de punteros¿Por qué no se puede usar Rc<T> para la transmisión de datos entre hilos?
Rc<T> no implementa el rasgo Send y no es thread-safe: la implementación interna se basa en un contador de referencias no bloqueante, lo que provoca una condición de carrera al acceder desde múltiples hilos. Para hilos, use Arc<T>.
¿Se puede implementar manualmente Send o Sync para un tipo propio para eludir las restricciones del compilador?
Se puede, ¡pero es extremadamente peligroso! Si se violan los invariantes (por ejemplo, compartir un puntero desnudo), se produce una condición de carrera. Deje la implementación manual únicamente para especialistas completamente seguros de la seguridad de hilos del tipo.
¿Cuándo puede un Mutex provocar un deadlock en Rust, y cómo evitarlo?
Un deadlock es posible si el orden de adquisición de múltiples mutex no es estable o si el bloqueo se anida recursivamente en un hilo (¡Mutex no es reentrante!).
use std::sync::Mutex; let a = Mutex::new(0); let _g1 = a.lock().unwrap(); let _g2 = a.lock().unwrap(); // panic: deadlock!
Un desarrollador utilizó Rc<RefCell<T>> para transmitir el estado entre hilos en un servidor web. Funcionó en pruebas locales, pero en producción aparecieron condiciones de carrera: a veces las variables "perdían" estado, a veces el servidor se caía.
Ventajas:
Desventajas:
Uso de Arc<Mutex<T>> al transmitir estado, estricta adherencia a Send/Sync, distribución del trabajo entre hilos a través de canales, sin mutación de datos compartidos entre hilos.
Ventajas:
Desventajas: