[...]let data = data.clone() 上发生了什么?
Arc 代表 Atomically Reference Counted。一个Arc 管理一个 对象(T 类型)并充当代理以允许共享所有权,这意味着:一个对象由多个名称拥有。哇,这听起来很抽象,让我们分解一下!
共享所有权
假设您有一个类型为 Turtle ? 的对象,它是您为家人购买的。现在问题出现了,您无法指定乌龟的明确所有者:每个家庭成员都拥有那只宠物!这意味着(并且很抱歉在这里病态)如果家庭中的一个成员死了,乌龟不会和那个家庭成员一起死。只有当家庭的所有成员都离开时,乌龟才会死。 每个人都拥有,最后一个清理干净。
那么您将如何在 Rust 中表达这种共享所有权?您很快就会注意到,仅使用标准方法是不可能的:您总是必须选择一个所有者,而其他所有人都只能引用海龟。不好!
所以Rc 和Arc 来了(为了这个故事,它们的目的完全相同)。这些允许通过修改 unsafe-Rust 来实现共享所有权。让我们看看执行以下代码后的内存(注意:内存布局用于学习,可能与现实世界的内存布局不完全相同):
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 书中所说的拥有的句柄:这种句柄的每个所有者也都拥有底层对象。
(另见"Why is std::rc::Rc<> not Copy?")
原子性和可变性
你问Arc<T>和Rc<T>有什么区别? Arc 以原子方式递增和递减其计数器。这意味着多个线程可以同时递增和递减计数器而不会出现问题。这就是为什么您可以跨线程边界发送Arcs,但不能发送Rcs。
现在您注意到您无法通过Arc<T> 改变数据!如果你的?失去一条腿怎么办? Arc 并非旨在允许多个所有者同时(可能)同时进行可变访问。这就是为什么你经常看到像Arc<Mutex<T>> 这样的类型。 Mutex<T> 是一种提供内部可变性的类型,这意味着您可以从&Mutex<T> 获得&mut T!这通常会与 Rust 核心原则相冲突,但它是完全安全的,因为互斥体也管理访问:您必须请求访问对象。如果另一个线程/源当前可以访问该对象,则您必须等待。因此,在某一特定时刻,只有一个线程能够访问T。
结论
[...] 是不是每个线程都在修改原始数据而不是副本?
正如您希望从上面的解释中理解的那样:是的,每个线程都在修改原始数据。在Arc<T> 上的clone() 不会克隆T,而只是创建另一个拥有的句柄;反过来,它只是一个指针,其行为就像它拥有底层对象一样。