ProgramaciónDesarrollador de bibliotecas de sistema / Desarrollador Backend

¿Cómo funciona la «seguridad de la concurrencia» (Send y Sync) en Rust, y cómo se pueden implementar/limitar para tipos personalizados?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

En Rust, la seguridad al trabajar con hilos se garantiza mediante dos rasgos automáticos: Send y Sync.

  • Send permite transferir un tipo entre hilos (a través de la transferencia de propiedad).
  • Sync garantiza que el tipo se pueda utilizar de forma segura desde varios hilos simultáneamente (a través de referencias).

La mayoría de los tipos estándar en Rust implementan estos rasgos por defecto. Por ejemplo, Arc<T>, Mutex<T> son Send y Sync (si T también cumple con estos rasgos).

Puedes prohibir o implementar explícitamente estos rasgos para tus tipos. Por ejemplo, si tienes un campo interno no seguro (como un puntero bruto o recursos externos), debes hacer que los tipos sean !Send o !Sync:

use std::marker::PhantomData; use std::rc::Rc; struct MyType { not_thread_safe: Rc<u32>, _marker: PhantomData<*const ()>, } // Rc<u32> no implementa Send/Sync, por lo que MyType tampoco implementará estos rasgos.

Si implementas un envoltorio de bajo nivel y gestionas la seguridad de los hilos manualmente, puedes implementar Send/Sync manualmente (unsafe):

unsafe impl Send for MyType {} unsafe impl Sync for MyType {}

Es responsabilidad del programador proporcionar garantías de seguridad en la concurrencia.

Pregunta capciosa.

¿Qué sucederá si sacamos Rc<T> en varios hilos a través de Arc<Rc<T>>?

A menudo se piensa que Arc protege todo. Pero Rc<T> no implementa Send/Sync, ¡incluso si lo envuelves en Arc! Así:

use std::rc::Rc; use std::sync::Arc; fn main() { let data = Arc::new(Rc::new(5)); // std::thread::spawn(move || { // println!("{:?}", data); // }); // ¡El compilador no permitirá hacerlo! }

Arc no compensa la falta de Send/Sync en los miembros internos.

Ejemplos de errores reales debido al desconocimiento de los matices del tema.


Historia

En el proyecto hubo un intento de usar Arc<Rc<T>> para compartir datos entre hilos y dividir la propiedad de manera no bloqueante. El programa fallaba durante la ejecución con un comportamiento impredecible; resultó que Rc no es seguro para hilos y la falta de conocimiento sobre los rasgos Send/Sync fue lo que causó este problema.


Historia

En un bucle de eventos hecho a mano, el tipo State almacenaba un puntero crudo a los datos. El tipo State fue marcado como unsafe impl Send, pero olvidaron implementar la sincronización. Como resultado, surgió una clásica data race, que se identificó después del lanzamiento.


Historia

Un desarrollador implementó un envoltorio newtype sobre Mutex, pero erróneamente lo hizo !Sync, sin implementar Sync manualmente. Esto impedía usar el tipo en un contexto de concurrencia (por ejemplo, dentro de Arc<Mutex<T>>), ya que el compilador exigía Sync. Se solucionó implementando unsafe impl Sync y analizando la seguridad.