Ответ 1
[...] что происходит на
let data = data.clone()
?
Arc
обозначает A текст R C. Arc
управляет одним объектом (типа T
) и служит прокси-сервером для разрешения совместного использования, что означает: один объект принадлежит нескольким именам. Вау, это звучит абстрактно, пусть сломает его!
Совместное владение
Скажем, у вас есть объект типа Turtle
🐢, который вы купили для своей семьи. Теперь возникает проблема, что вы не можете назначить четкого владельца черепахи: каждый член семьи является владельцем этого питомца! Это означает (и извините за то, что здесь было болезненно), что если один член семьи умрет, черепаха не умрет вместе с этим членом семьи. Черепаха будет умирать, только если все члены семьи уйдут. Каждый человек владеет, а последний очищает.
Итак, как бы вы выразили такую совместную собственность в Rust? Вы быстро заметите, что это невозможно сделать только с помощью стандартных методов: вам всегда нужно выбирать одного владельца, а все остальные будут иметь ссылку на черепаху. Нехорошо!
Итак, придем Rc
и Arc
(что для этой истории служит той же цели). Они позволяют использовать совместное владение, слегка перебирая небезопасную-ржавчину. Посмотрите на память после выполнения следующего кода (обратите внимание: макет памяти предназначен для обучения и может не представлять собой то же самое расположение памяти из реального мира):
let annas = Rc::new(Turtle { legs: 4 });
Память:
Stack Heap
----- ----
annas:
+--------+ +------------+
| ptr: o-|-------------->| count: 1 |
+--------+ | data: 🐢 |
+------------+
Мы видим, что черепаха живет в куче... рядом с счетчиком, который установлен в 1. Этот счетчик знает, сколько владельцев объекта data
в настоящее время. И 1 правильно: annas
- единственный, владеющий черепахой прямо сейчас. Пусть clone()
Rc
, чтобы получить больше владельцев:
let peters = annas.clone();
let bobs = annas.clone();
Теперь память выглядит так:
Stack Heap
----- ----
annas:
+--------+ +------------+
| ptr: o-|-------------->| count: 3 |
+--------+ ^ | data: 🐢 |
| +------------+
peters: |
+--------+ |
| ptr: o-|----+
+--------+ ^
|
bobs: |
+--------+ |
| ptr: o-|----+
+--------+
Как вы можете видеть, черепаха все еще существует только один раз. Но счетчик ссылок был увеличен и теперь составляет 3, что имеет смысл, потому что у черепахи сейчас три владельца. Все эти три владельца ссылаются на этот блок памяти на кучу. Это то, что книга Rust называет принадлежащим дескриптору: каждый владелец такого дескриптора также имеет вид собственного объекта.
(также см. "Почему std::rc::Rc<>
не копировать?" )
Атомарность и мутность
Какую разницу между Arc<T>
и Rc<T>
вы спрашиваете? Arc
увеличивает и уменьшает счетчик в атомном режиме. Это означает, что несколько потоков могут увеличивать и уменьшать счетчик одновременно без проблем. Вот почему вы можете отправлять Arc
через границы потоков, но не Rc
s.
Теперь вы заметили, что не можете мутировать данные через Arc<T>
! Что делать, если ваш 🐢 теряет ногу? Arc
не предназначен для разрешенного доступа нескольких владельцев (возможно) в одно и то же время. Вот почему вы часто видите такие типы, как Arc<Mutex<T>>
. Mutex<T>
- это тип, который предлагает внутреннюю изменчивость, что означает, что вы можете получить &mut T
от &Mutex<T>
! Это обычно противоречит основным принципам Rust, но совершенно безопасно, поскольку мьютекс также управляет доступом: вы должны запрашивать доступ к объекту. Если другой поток/источник в настоящее время имеет доступ к объекту, вам нужно подождать. Поэтому в один данный момент времени существует только один поток, доступ к которому можно получить T
.
Заключение
[...] - каждый поток изменяет исходные данные вместо копии?
Как вы можете, надеюсь, понять из приведенного выше объяснения: да, каждый поток изменяет исходные данные. A clone()
на Arc<T>
не будет клонировать T
, а просто создает другой принадлежащий ему дескриптор; который, в свою очередь, является просто указателем, который ведет себя так, как будто он владеет базовым объектом.