concurrency - vehiculos - que hacer en caso de comprar un auto robado
¿Qué sucede cuando se clona un arco? (3)
Estoy aprendiendo la concurrencia y quiero aclarar mi comprensión en el siguiente ejemplo de código del libro Rust . Por favor, corríjame si estoy equivocado.
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
for i in 0..3 {
let data = data.clone();
thread::spawn(move || {
let mut data = data.lock().unwrap();
data[0] += i;
});
}
thread::sleep(Duration::from_millis(50));
}
¿Qué está sucediendo en la línea let data = data.clone()
?
El libro de Rust dice
usamos
clone()
para crear un nuevo manejador propio. Este asa se mueve entonces al nuevo hilo.
¿Cuál es el nuevo "mango propio"? ¿Suena como una referencia a los datos?
Dado que el clone
toma un &self
y devuelve un Self
, ¿cada hilo modifica los datos originales en lugar de una copia? Supongo que es por eso que el código no usa data.copy()
sino data.clone()
aquí.
Los data
en el lado derecho son una referencia, y los data
en el lado izquierdo son valores propios. Hay una variable de sombra aquí.
[...] ¿qué está pasando en
let data = data.clone()
?
Arc
sinónimo de referencia tomicamente. Un Arc
administra un objeto (de tipo T
) y sirve como un proxy para permitir la propiedad compartida , lo que significa que: un objeto es propiedad de varios nombres. Wow, eso suena abstracto, vamos a descomponerlo!
Propiedad compartida
Digamos que tiene un objeto de tipo Turtle
🐢 que compró para su familia. Ahora surge el problema de que no se puede asignar un propietario claro de la tortuga: ¡cada miembro de la familia posee esa mascota! Esto significa (y lo siento por ser morboso aquí) que si un miembro de la familia muere, la tortuga no morirá con ese miembro de la familia. La tortuga solo morirá si todos los miembros de la familia también se han ido. Todos son dueños y el último se limpia .
Entonces, ¿cómo expresarías ese tipo de propiedad compartida en Rust? Notará rápidamente que es imposible hacerlo solo con métodos estándar: siempre tendría que elegir un propietario y todos los demás solo tendrían una referencia a la tortuga. ¡No está bien!
Así que a lo largo vienen Rc
y Arc
(que, por el bien de esta historia, sirven exactamente para el mismo propósito). Estos permiten la propiedad compartida al jugar un poco con unsafe-Rust. Veamos la memoria después de ejecutar el siguiente código ( nota : el diseño de la memoria es para el aprendizaje y podría no representar exactamente el mismo diseño de memoria del mundo real):
let annas = Rc::new(Turtle { legs: 4 });
Memoria:
Stack Heap
----- ----
annas:
+--------+ +------------+
| ptr: o-|-------------->| count: 1 |
+--------+ | data: 🐢 |
+------------+
Vemos que la tortuga vive en el montón ... junto a un contador que se establece en 1. Este contador sabe cuántos propietarios tienen actualmente los data
del objeto. Y 1 es correcto: annas
es el único que posee la tortuga en este momento. Vamos a clone()
el Rc
para obtener más propietarios:
let peters = annas.clone();
let bobs = annas.clone();
Ahora la memoria se ve así:
Stack Heap
----- ----
annas:
+--------+ +------------+
| ptr: o-|-------------->| count: 3 |
+--------+ ^ | data: 🐢 |
| +------------+
peters: |
+--------+ |
| ptr: o-|----+
+--------+ ^
|
bobs: |
+--------+ |
| ptr: o-|----+
+--------+
Como puedes ver, la tortuga todavía existe solo una vez. Pero el recuento de referencias aumentó y ahora es 3, lo que tiene sentido, porque la tortuga tiene tres dueños ahora. Los tres propietarios hacen referencia a este bloque de memoria en el montón. Eso es lo que el libro de Rust llama asidero : cada propietario de tal asa también posee el tipo de objeto subyacente.
( consulte también "¿Por qué std::rc::Rc<>
no es Copiar?" )
Atomicidad y mutabilidad
¿Cuál es la diferencia entre Arc<T>
y Rc<T>
que preguntaste? El Arc
aumenta y disminuye su contador de forma atómica. Eso significa que múltiples hilos pueden incrementar y disminuir el contador simultáneamente sin problemas. Es por eso que puede enviar Arc
s a través de límites de hilos, pero no Rc
s.
¡Ahora se da cuenta de que no puede mutar los datos a través de un Arc<T>
! ¿Qué pasa si tu 🐢 pierde una pierna? Arc
no está diseñado para permitir el acceso mutable de múltiples propietarios al mismo tiempo (posiblemente). Es por eso que a menudo ves tipos como Arc<Mutex<T>>
. El Mutex<T>
es un tipo que ofrece mutabilidad interior , lo que significa que puede obtener un &mut T
de un &Mutex<T>
. Esto normalmente entraría en conflicto con los principios básicos de Rust, pero es perfectamente seguro porque el mutex también administra el acceso: usted debe solicitar acceso al objeto. Si otro hilo / fuente actualmente tiene acceso al objeto, debe esperar. Por lo tanto, en un momento dado en el tiempo, solo hay un hilo capaz de acceder a T
Conclusión
[...] ¿cada hilo modifica los datos originales en lugar de una copia?
Como es de esperar que puedas entender de la explicación anterior: sí, cada hilo está modificando los datos originales. Un clone()
en un Arc<T>
no clonará la T
, sino que simplemente creará otro manejador propio ; que a su vez es solo un puntero que se comporta como si fuera el objeto subyacente.
No soy un experto en las bibliotecas internas estándar y todavía estoy aprendiendo Rust ... pero esto es lo que puedo ver: ( usted también podría verificar la fuente si lo desea ).
En primer lugar, una cosa importante para recordar en Rust es que en realidad es posible salir de los "límites seguros" que proporciona el compilador, si sabe lo que está haciendo. Por lo tanto, tratar de razonar acerca de cómo funcionan internamente algunos de los tipos de bibliotecas estándar, con el sistema de propiedad como su base de comprensión puede no tener mucho sentido.
Arc
es uno de los tipos de biblioteca estándar que elude internamente el sistema de propiedad. Básicamente, gestiona un puntero por sí mismo y la llamada clone()
devuelve un nuevo Arc
que apunta exactamente a la misma pieza de memoria que el original ... con un recuento de referencia incrementado.
Entonces, en un nivel alto, sí, clone()
devuelve una nueva instancia de Arc
y la propiedad de esa nueva instancia se mueve al lado izquierdo de la asignación. Sin embargo, internamente, la nueva instancia de Arc
aún apunta a donde lo hizo la antigua ... a través de un puntero en bruto (o tal como aparece en la fuente, a través de una instancia Shared
, que es un envoltorio alrededor de un puntero en bruto). El envoltorio alrededor del puntero en bruto es lo que imagino que se refiere a la documentación como un "identificador propio".
std::sync::Arc
es un puntero inteligente , uno que agrega las siguientes habilidades:
Una envoltura contada de referencia atómicamente para el estado compartido.
Arc
(y su amigo std::rc::Rc
) que no es seguro para subprocesos permiten la propiedad compartida . Eso significa que múltiples "controladores" apuntan al mismo valor. Cada vez que se clona un controlador, se incrementa un contador de referencia . Cada vez que se deja caer una palanca, el contador se decrementa. Cuando el contador llega a cero, se libera el valor al que apuntaban los controladores.
Tenga en cuenta que este puntero inteligente no llama al método de clone
subyacente de los datos; de hecho, puede que no sea necesario que haya un método de clone
subyacente. Arc
maneja lo que sucede cuando se llama clone
.
¿Cuál es el nuevo "mango propio"? ¿Suena como una referencia a los datos?
Es a la vez y no es una referencia. En la programación más amplia y en el sentido en inglés de la palabra "referencia", es una referencia . En el sentido específico de una referencia de Rust ( &Foo
), no es una referencia . Confuso, ¿verdad?
La segunda parte de su pregunta es sobre std::sync::Mutex
, que se describe como:
Una primitiva de exclusión mutua útil para proteger datos compartidos.
Los Mutexes son herramientas comunes en programas de subprocesos múltiples, y están bien descritos en otros lugares, así que no me molestaré en repetir eso aquí. Lo importante a tener en cuenta es que un Rust Mutex
solo le brinda la capacidad de modificar el estado compartido. Arc
de Arc
permitir que múltiples propietarios tengan acceso al Mutex
para intentar modificar el estado.
Esto es un poco más detallado que otros idiomas, pero permite que estas piezas se reutilicen de formas novedosas.