Arc::make_mut intenta proporcionar acceso mutable a los datos internos verificando primero que el Arc tenga la única referencia fuerte a la asignación. Realiza esta verificación utilizando una carga atómica con un orden de Acquire en el conteo de referencias fuertes. Si el conteo es exactamente uno, la operación procede a devolver una referencia mutable; de lo contrario, clona los datos internos y actualiza el Arc para que apunte a la nueva asignación.
use std::sync::Arc; let mut data = Arc::new(5); *Arc::make_mut(&mut data) += 1; // Clona solo si está compartido
El par Acquire/Release es esencial porque cuando otro hilo elimina su Arc, realiza un decremento de Release en el conteo. La carga Acquire en make_mut asegura que todas las escrituras de memoria realizadas por el hilo que elimina antes del decremento sean visibles para el hilo actual, previniendo carreras de datos en los datos internos.
Considera un servicio de agregación de métricas de alto rendimiento donde las actualizaciones de configuración se propagan a través de Arc<Config>. Miles de hilos mantienen referencias para leer los ajustes actuales, pero el hilo del administrador necesita ajustar los umbrales periódicamente sin reiniciar el servicio.
El enfoque ingenuo es envolver el Config en un RwLock y bloquearlo para cada lectura, o clonar toda la estructura para cada actualización menor independientemente del uso compartido. La primera solución sufre de rebote de líneas de caché y sobrecarga de bloqueo, mientras que la segunda desperdicia memoria y ciclos de CPU en asignaciones redundantes cuando el config es realmente único.
Una alternativa es usar AtomicPtr con punteros de peligro para actualizaciones sin bloqueo, pero esto requiere una gestión de memoria manual compleja y propensa a errores. Otra opción es usar un RwLock<Arc<Config>>, permitiendo intercambios atómicos del puntero mismo, pero esto agrega una indireccionamiento y un bloqueo extra para el intercambio de punteros.
El equipo eligió Arc::make_mut porque optimiza para el caso común: si ningún otro hilo tiene una referencia (el conteo fuerte es 1), el hilo del administrador modifica los datos en su lugar sin asignación. Si la configuración está compartida, clona de manera transparente. Esto requiere las estrictas semánticas Acquire/Release para garantizar que cuando el último lector abandona su Arc (usando Release), la verificación posterior del hilo del administrador (usando Acquire) vea todas las escrituras anteriores a la configuración, previniendo lecturas desgarradas. El resultado fue una reducción del 40% en la latencia para las actualizaciones de configuración bajo baja contención.
¿Por qué no se puede usar el ordenamiento Relaxed para la verificación del conteo de referencias en Arc::make_mut?
Las operaciones Relaxed no proporcionan garantías de ocurre-anterior. Si make_mut usara Relaxed para verificar si el conteo fuerte es 1, podría observar el decremento del conteo de otro hilo antes de observar las escrituras de ese hilo en los datos internos. Esto permitiría que el hilo actual mutara los datos mientras otro hilo aún los está leyendo lógicamente, causando una carrera de datos. Acquire asegura que cuando vemos el conteo llegar a 1 (sincronizado a través del Release en el hilo de eliminación de otro hilo), también vemos todas las escrituras anteriores a los datos.
¿Qué distingue el comportamiento de Arc::make_mut de clonar manualmente el Arc con .clone() seguido de una modificación?
Clonar manualmente crea un nuevo Arc que apunta a la misma asignación, incrementando el conteo fuerte a al menos 2. No puedes obtener acceso mutable a los datos internos a través de este nuevo Arc porque Arc solo proporciona uso compartido inmutable. Arc::make_mut es especial porque verifica si el conteo es 1; si es así, proporciona &mut T a la asignación existente. Si no, clona los datos en una nueva asignación con un conteo de 1, asegurando que los datos compartidos originales permanezcan inmutables mientras te da la propiedad única de la nueva copia.
¿Cómo afectan los punteros débiles (Arc::downgrade) la garantía de unicidad de Arc::make_mut?
Los punteros débiles no participan en el conteo de referencias fuertes. Arc::make_mut solo verifica el conteo fuerte, ignorando referencias débiles. Sin embargo, los punteros débiles pueden ser promovidos a fuertes si la asignación aún existe. Si make_mut procede con mutación en su lugar (el conteo fuerte es 1), y otro hilo posteriormente promueve un puntero débil, esa promoción creará un nuevo Arc apuntando a los mismos datos mutados. Esto es seguro porque la promoción ocurre después de la mutación, y el modelo de memoria de Rust garantiza que el puntero promovido vea el valor completamente modificado. El conteo débil no previene la mutación, pero mantiene la asignación viva incluso si todas las referencias fuertes son eliminadas temporalmente.