diff --git a/docs/print.html b/docs/print.html
index 8398e6d..5a43c7c 100644
--- a/docs/print.html
+++ b/docs/print.html
@@ -2,7 +2,7 @@
@@ -8728,6 +8728,171 @@ values pointing to each other
Figure 15-18: A reference cycle of lists a
and b
pointing to each other
+
如果你注释掉最后的println!
,Rust 会尝试打印出a
指向b
指向a
这样的循环直到栈溢出。
+
观察最后一个println!
之前的打印结果,就会发现在将a
改变为指向b
之后a
和b
的引用计数都是 2。在main
的结尾,Rust 首先会尝试丢弃b
,这会使Rc
的引用计数减一,但是这个计数是 1 而不是 0,所以Rc
在堆上的内存不会被丢弃。它只是会永远的停留在 1 上。这个特定例子中,程序立马就结束了,所以并不是一个问题,不过如果是一个更加复杂的程序,它在这个循环中分配了很多内存并占有很长时间,这就是个问题了。这个程序会使用多于它所需要的内存,并有可能压垮系统并造成没有内存可供使用。
+
现在,如你所见,在 Rust 中创建引用循环是困难和繁琐的。但并不是不可能:避免引用循环这种形式的内存泄漏并不是 Rust 的保证之一。如果你有包含Rc<T>
的RefCell<T>
值或类似的嵌套结合了内部可变性和引用计数的类型,请务必小心确保你没有形成一个引用循环。在列表 15-14 的例子中,可能解决方式就是不要编写像这样可能造成引用循环的代码,因为我们希望Cons
成员拥有他们指向的列表。
+
举例来说,对于像图这样的数据结构,为了创建父节点指向子节点的边和以相反方向从子节点指向父节点的边,有时需要创建这样的引用循环。如果一个方向拥有所有权而另一个方向没有,对于模拟这种数据关系的一种不会创建引用循环和内存泄露的方式是使用Weak<T>
。接下来让我们探索一下!
+
+
Rust 标准库中提供了Weak<T>
,一个用于存在引用循环但只有一个方向有所有权的智能指针。我们已经展示过如何克隆Rc<T>
来增加引用的strong_count
;Weak<T>
是一种引用Rc<T>
但不增加strong_count
的方式:相反它增加Rc
引用的weak_count
。当Rc
离开作用域,其内部值会在strong_count
为 0 的时候被丢弃,即便weak_count
不为 0 。为了能够从Weak<T>
中获取值,首先需要使用upgrade
方法将其升级为Option<Rc<T>>
。升级Weak<T>
的结果在Rc
还未被丢弃时是Some
,而在Rc
被丢弃时是None
。因为upgrade
返回一个Option
,我们知道 Rust 会确保Some
和None
的情况都被处理并不会尝试使用一个无效的指针。
+
不同于列表 15-17 中每个项只知道它的下一项,加入我们需要一个树,它的项知道它的子项和父项。
+
让我们从一个叫做Node
的存放拥有所有权的i32
值和其子Node
值的引用的结构体开始:
+
use std::rc::Rc;
+use std::cell::RefCell;
+
+#[derive(Debug)]
+struct Node {
+ value: i32,
+ children: RefCell<Vec<Rc<Node>>>,
+}
+
+
我们希望能够Node
拥有其子节点,同时也希望变量可以拥有每个节点以便可以直接访问他们。这就是为什么Vec
中的项是Rc<Node>
值。我们也希望能够修改其他节点的子节点,这就是为什么children
中Vec
被放进了RefCell
的原因。在列表 15-19 中创建了一个叫做leaf
的带有值 3 并没有子节点的Node
实例,和另一个带有值 5 和以leaf
作为子节点的实例branch
:
+
Filename: src/main.rs
+
fn main() {
+ let leaf = Rc::new(Node {
+ value: 3,
+ children: RefCell::new(vec![]),
+ });
+
+ let branch = Rc::new(Node {
+ value: 5,
+ children: RefCell::new(vec![leaf.clone()]),
+ });
+}
+
+
Listing 15-19: Creating a leaf
node and a branch
node
+where branch
has leaf
as one of its children but leaf
has no reference to
+branch
+
leaf
中的Node
现在有两个所有者:leaf
和branch
,因为我们克隆了leaf
中的Rc
并储存在了branch
中。branch
中的Node
知道它与leaf
相关联因为branch
在branch.children
中有leaf
的引用。然而,leaf
并不知道它与branch
相关联,而我们希望leaf
知道branch
是其父节点。
+
为了做到这一点,需要在Node
结构体定义中增加一个parent
字段,不过parent
的类型应该是什么呢?我们知道它不能包含Rc<T>
,因为这样leaf.parent
将会指向branch
而branch.children
会包含leaf
的指针,这会形成引用循环。leaf
和branch
不会被丢弃因为他们总是引用对方且引用计数永远也不会是零。
+
所以在parent
的类型中是使用Weak<T>
而不是Rc
,具体来说是RefCell<Weak<Node>>
:
+
Filename: src/main.rs
+
use std::rc::{Rc, Weak};
+use std::cell::RefCell;
+
+#[derive(Debug)]
+struct Node {
+ value: i32,
+ parent: RefCell<Weak<Node>>,
+ children: RefCell<Vec<Rc<Node>>>,
+}
+
+
这样,一个节点就能够在拥有父节点时指向它,而并不拥有其父节点。一个父节点哪怕在拥有指向它的子节点也会被丢弃,只要是其自身也没有一个父节点就行。现在将main
函数更新为如列表 15-20 所示:
+
Filename: src/main.rs
+
fn main() {
+ let leaf = Rc::new(Node {
+ value: 3,
+ parent: RefCell::new(Weak::new()),
+ children: RefCell::new(vec![]),
+ });
+
+ println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
+
+ let branch = Rc::new(Node {
+ value: 5,
+ parent: RefCell::new(Weak::new()),
+ children: RefCell::new(vec![leaf.clone()]),
+ });
+
+ *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
+
+ println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
+}
+
+
Listing 15-20: A leaf
node and a branch
node where
+leaf
has a Weak
reference to its parent, branch
+
创建leaf
节点是类似的;因为它作为开始并没有父节点,这里创建了一个新的Weak
引用实例。当尝试通过upgrade
方法获取leaf
父节点的引用时,会得到一个None
值,如第一个println!
输出所示:
+
leaf parent = None
+
+
类似的,branch
也有一个新的Weak
引用,因为也没有父节点。leaf
仍然作为branch
的一个子节点。一旦在branch
中有了一个新的Node
实例,就可以修改leaf
将一个branch
的Weak
引用作为其父节点。这里使用了leaf
中parent
字段里的RefCell
的borrow_mut
方法,接着使用了Rc::downgrade
函数来从branch
中的Rc
值创建了一个指向branch
的Weak
引用。
+
当再次打印出leaf
的父节点时,这一次将会得到存放了branch
的Some
值。另外需要注意到这里并没有打印出类似列表 15-14 中那样最终导致栈溢出的循环:Weak
引用仅仅打印出(Weak)
:
+
leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
+children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
+children: RefCell { value: [] } }] } })
+
+
没有无限的输出(或直到栈溢出)的事实表明这里并没有引用循环。另一种证明的方式时观察调用Rc::strong_count
和Rc::weak_count
的值。在列表 15-21 中,创建了一个新的内部作用域并将branch
的创建放入其中,这样可以观察branch
被创建时和离开作用域被丢弃时发生了什么:
+
Filename: src/main.rs
+
fn main() {
+ let leaf = Rc::new(Node {
+ value: 3,
+ parent: RefCell::new(Weak::new()),
+ children: RefCell::new(vec![]),
+ });
+
+ println!(
+ "leaf strong = {}, weak = {}",
+ Rc::strong_count(&leaf),
+ Rc::weak_count(&leaf),
+ );
+
+ {
+ let branch = Rc::new(Node {
+ value: 5,
+ parent: RefCell::new(Weak::new()),
+ children: RefCell::new(vec![leaf.clone()]),
+ });
+ *leaf.parent.borrow_mut() = Rc::downgrade(&branch);
+
+ println!(
+ "branch strong = {}, weak = {}",
+ Rc::strong_count(&branch),
+ Rc::weak_count(&branch),
+ );
+
+ println!(
+ "leaf strong = {}, weak = {}",
+ Rc::strong_count(&leaf),
+ Rc::weak_count(&leaf),
+ );
+ }
+
+ println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
+ println!(
+ "leaf strong = {}, weak = {}",
+ Rc::strong_count(&leaf),
+ Rc::weak_count(&leaf),
+ );
+}
+
+
Listing 15-21: Creating branch
in an inner scope and
+examining strong and weak reference counts of leaf
and branch
+
创建leaf
之后,强引用计数是 1 (用于leaf
自身)而弱引用计数是 0。在内部作用域中,在创建branch
和关联leaf
和branch
之后,branch
的强引用计数为 1(用于branch
自身)而弱引用计数为 1(因为leaf.parent
通过一个Weak<T>
指向branch
)。leaf
的强引用计数为 2,因为branch
现在有一个leaf
克隆的Rc
储存在branch.children
中。leaf
的弱引用计数仍然为 0。
+
当内部作用域结束,branch
离开作用域,其强引用计数减少为 0,所以其Node
被丢弃。来自leaf.parent
的弱引用计数 1 与Node
是否被丢弃无关,所以并没有产生内存泄露!
+
如果在内部作用域结束后尝试访问leaf
的父节点,会像leaf
拥有父节点之前一样得到None
值。在程序的末尾,leaf
的强引用计数为 1 而弱引用计数为 0,因为现在leaf
又是唯一指向其自己的值了。
+
所有这些管理计数和值是否应该被丢弃的逻辑都通过Rc
和Weak
和他们的Drop
trait 实现来控制。通过在定义中指定从子节点到父节点的关系为一个Weak<T>
引用,就能够拥有父节点和子节点之间的双向引用而不会造成引用循环和内存泄露。
+
+
现在我们学习了如何选择不同类型的智能指针来选择不同的保证并与 Rust 的常规引用向取舍。Box<T>
有一个已知的大小并指向分配在堆上的数据。Rc<T>
记录了堆上数据的引用数量这样就可以拥有多个所有者。RefCell<T>
和其内部可变性使其可以用于需要不可变类型,但希望在运行时而不是编译时检查借用规则的场景。
+
我们还介绍了提供了很多智能指针功能的 trait Deref
和Drop
。同时探索了形成引用循环和造成内存泄漏的可能性,以及如何使用Weak<T>
避免引用循环。
+
如果本章内容引起了你的兴趣并希望现在就实现你自己的智能指针的话,请阅读 The Nomicon 来获取更多有用的信息。
+
接下来,让我们谈谈 Rust 的并发。我们还会学习到一些新的堆并发有帮助的智能指针。
+
+
+ch16-00-concurrency.md
+
+commit da15de39eaabd50100d6fa662c653169254d9175
+
+
确保内存安全并不是 Rust 的唯一目标:作为一个能更好的处理并发和并行编程一直是 Rust 的另一个主要目标。
+并发编程(concurrent programming)代表程序的不同部分相互独立的执行,而并行编程代表程序不同部分同时执行,这两个概念在计算机拥有更多处理器可供程序利用时变得更加重要。由于历史的原因,在此类上下文中编程一直是困难且容易出错的:Rust 希望能改变这一点。
+
最开始,我们认为内存安全和防止并发问题是需要通过两个不同的方法解决的两个相互独立的挑战。然而,随着时间的推移,我们发现所有权和类型系统是一系列解决内存安全和并发问题的强用力的工具!通过改进所有权和类型检查,很多并发错误在 Rust 中都是编译时错误,而不是运行时错误。我们给 Rust 的这一部分起了一个绰号无畏并发(fearless concurrency)。无畏并发意味着 Rust 不光允许你自信代码不会出现诡异的错误,也让你可以轻易重构这种代码而无需担心会引入新的 bug。
+
+注意:对于 Rust 的口号无畏并发,这里用并发指代很多问题而不是更精确的区分并发和(或)并行,是处于简化问题的原因。如果这是一本专注于并发和/或并行的书,我们肯定会更精确的。对于本章,请自行脑补任何并发为并发和(或)并行。
+
+
很多语言对于其所提供的处理并发并发问题的解决方法是非常固执己见的。这是一个非常合理的策略,尤其是对于更高级的语言来说,不过对于底层语言来说可没有奢侈的选择。底层语言被期望为能在任何给定的场景中启用提供最高性能的方法,同时他们对硬件有更少的抽象。因此,Rust 给了我们多种工具来以适合场景和要求的方式来为问题建模。
+
如下是本章将要涉及到的内容:
+
+- 如果创建线程来同时运行多段代码。
+- 并发消息传递(Message passing),其中通道(channel)被用来在线程间传递消息。
+- 并发共享状态(Shared state),其中多个线程可以访问同一片数据。
+Sync
和Send
trait,他们允许 Rust 的并发保证能扩展到用户定义的和标准库中提供的类型中。
+
+
+
+ch16-01-threads.md
+
+commit 55b294f20fc846a13a9be623bf322d8b364cee77
+
+
在今天使用的大部分操作系统中,当程序执行时,操作系统运行代码的上下文称为进程(process)。操作系统可以运行很多进程,而操作系统也管理这些进程使得多个程序可以在电脑上同时运行。
+
我们可以将每个进程运行一个程序的概念再往下抽象一层:程序也可以在其上下文中同时运行独立的部分。这个功能叫做线程(thread)。
+
将程序需要执行的计算拆分到多个线程中可以提高性能,因为程序可以在同时进行很多工作。
diff --git a/src/SUMMARY.md b/src/SUMMARY.md
index 620a10e..841e239 100644
--- a/src/SUMMARY.md
+++ b/src/SUMMARY.md
@@ -84,4 +84,10 @@
- [`Drop` Trait 运行清理代码](ch15-03-drop.md)
- [`Rc