问题描述
我正在学习并发性,想澄清一下我对以下code example from the Rust book的理解。如果我说错了,请纠正我。
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));
}
线路let data = data.clone()
上发生了什么?
铁锈书上说
新的"拥有的句柄"是什么?听起来像是对数据的引用?
由于clone
获取&self
并返回Self
,每个线程是否修改原始数据而不是副本?我猜这就是代码没有使用data.copy()
而是这里使用data.clone()
的原因。右侧的data
为参照,左侧的data
为自有值。这里有一个变量跟踪。推荐答案
Arc
代表AR参考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书中所说的拥有句柄:这样的句柄的每个所有者也在某种程度上拥有底层对象。
(另见"Why is std::rc::Rc<>
not Copy?")
原子性和突变性
您问Arc<T>
和Rc<T>
有什么区别?Arc
以原子方式递增和递减其计数器。这意味着多个线程可以同时递增和递减计数器,而不会出现问题。这就是为什么您可以跨线程边界发送Arc
,但不能发送Rc
s。现在您注意到,您不能通过Arc<T>
来更改数据!如果您的🐢失去了一条腿怎么办?Arc
的设计不允许多个所有者(可能)同时进行可变访问。这就是为什么您经常看到Arc<Mutex<T>>
这样的类型。Mutex<T>
是一种提供内部可变性的类型,这意味着您可以从&Mutex<T>
获得&mut T
!这通常会与Rust核心原则冲突,但它是绝对安全的,因为互斥锁还管理访问:您必须请求访问对象。如果另一个线程/源当前可以访问该对象,则必须等待。因此,在某个给定时刻,只有一个线程能够访问T
。结论
您可以从上面的解释中了解到:是的,每个线程都在修改原始数据。Arc<T>
上的clone()
不会克隆T
,而只是创建另一个拥有的句柄;而该句柄又只是一个指针,其行为就像它拥有基础对象一样。 这篇关于克隆Arc时会发生什么情况?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!