Merge pull request #364 from JesseAtSZ/patch-16

Update circle-reference.md
pull/370/head
Sunface 3 years ago committed by GitHub
commit 668bf88bb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,8 +1,8 @@
# Weak与循环引用
Rust的安全性是众所周知的但是不代表它不会内存泄漏。一个典型的例子就是同时使用`Rc<T>`和`RefCell<T>`创建循环引用,最终这些引用的计数都无法被归零,因此`Rc<T>`拥有的值也不会被释放清理。
# Weak 与循环引用
Rust 的安全性是众所周知的,但是不代表它不会内存泄漏。一个典型的例子就是同时使用 `Rc<T>``RefCell<T>` 创建循环引用,最终这些引用的计数都无法被归零,因此 `Rc<T>` 拥有的值也不会被释放清理。
## 何为循环引用
关于内存泄漏如果你没有充足的Rust经验可能都无法造出一份代码来再现它:
关于内存泄漏,如果你没有充足的 Rust 经验,可能都无法造出一份代码来再现它
```rust
use crate::List::{Cons, Nil};
use std::cell::RefCell;
@ -26,13 +26,13 @@ impl List {
fn main() {}
```
这里我们创建一个有些复杂的枚举类型`List`,这个类型很有意思,它的每个值都指向了另一个`List`,而且得益于`Rc`的使用还允许多个值指向一个`List`:
这里我们创建一个有些复杂的枚举类型 `List`,这个类型很有意思,它的每个值都指向了另一个 `List`,此外,得益于 `Rc` 的使用还允许多个值指向一个 `List`
<img alt="" src="/img/self-ref-01.png" class="center" />
如上图所示,每个矩形框节点都是一个`List`类型,它们或者是拥有值且指向另一个`List`的的`Cons`,或者是一个没有值的终结点`Nil`。同时,由于`RefCell`的使用,每个`List`所指向的`List`还能够被修改。
如上图所示,每个矩形框节点都是一个 `List` 类型,它们或者是拥有值且指向另一个 `List` 的`Cons`,或者是一个没有值的终结点 `Nil`。同时,由于 `RefCell` 的使用,每个 `List` 所指向的 `List` 还能够被修改。
下面来使用一下这个复杂的`List`枚举:
下面来使用一下这个复杂的 `List` 枚举:
```rust
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
@ -62,12 +62,12 @@ fn main() {
```
这个类型定义看着复杂,使用起来更复杂!不过排除这些因素,我们可以清晰看出:
1. 在创建了`a`后,紧接着就使用`a`创建了`b`,因此`b`引用了`a`
2. 然后我们又利用`Rc`克隆了`b`,然后通过`RefCell`的可变性,让`a`引用了`b`
1. 在创建了 `a` 后,紧接着就使用 `a` 创建了 `b`,因此 `b` 引用了 `a`
2. 然后我们又利用 `Rc` 克隆了 `b`,然后通过 `RefCell` 的可变性,让 `a` 引用了 `b`
至此我们成功创建了循环引用`a`-> `b` -> `a` -> `b` ····
先来观察下引用计数:
先来观察下引用计数
```console
a的初始化rc计数 = 1
a指向的节点 = Some(RefCell { value: Nil })
@ -78,9 +78,9 @@ b指向的节点 = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
在更改a后a的rc计数 = 2
```
在`main`函数结束前,`a`和`b`的引用计数均是`2`,随后`b`触发`Drop`,此时引用计数会变为`1`,并不会归`0`,因此`b`所指向内存不会被释放,同理可得`a`指向的内存也不会被释放,最终发生了内存泄漏。
`main` 函数结束前,`a` `b` 的引用计数均是 `2`,随后 `b` 触发 `Drop`,此时引用计数会变为 `1`,并不会归 `0`,因此 `b` 所指向内存不会被释放,同理可得 `a` 指向的内存也不会被释放,最终发生了内存泄漏。
下面一张图很好的展示了这种引用循环关系:
下面一张图很好的展示了这种引用循环关系
<img alt="" src="/img/self-ref-02.png" class="center" />
现在我们还需要轻轻的推一下,让塔米诺骨牌轰然倒塌。反注释最后一行代码,试着运行下:
@ -91,23 +91,23 @@ thread 'main' has overflowed its stack
fatal runtime error: stack overflow
```
通过`a.tail`的调用Rust试图打印出`a -> b ->a···`的所有内容,但是在不懈的努力后,`main`线程终于不堪重负,发生了[栈溢出](https://course.rs/pitfalls/stack-overflow.html)。
通过 `a.tail` 的调用Rust 试图打印出 `a -> b ->a···` 的所有内容,但是在不懈的努力后,`main` 线程终于不堪重负,发生了[栈溢出](https://course.rs/pitfalls/stack-overflow.html)。
以上的代码可能并不会造成什么大的问题,但是在一个更加复杂的程序中,类似的问题可能会造成你的程序不断分配内存、泄漏内存,最终程序会不幸**OOM**当然这其中的CPU损耗也不可小觑。
以上的代码可能并不会造成什么大的问题,但是在一个更加复杂的程序中,类似的问题可能会造成你的程序不断分配内存、泄漏内存,最终程序会不幸**OOM**,当然这其中的 CPU 损耗也不可小觑。
总之,创建引用并不简单,但是也并不是完全遇不到,当你使用`RefCell<Rc<T>>`或者类似的类型嵌套组合(具备内部可变性和引用计数)时,就要打起万分精神,前面可能是深渊!
那么问题来了? 如果我们确实需要实现上面的功能,该怎么办?答案是使用`Weak`。
## Weak
`Weak`非常类似于`Rc`,但是与`Rc`持有所有权不同,`Weak`不持有所有权,它仅仅保存一份指向数据的弱引用:如果你想要访问数据,需要通过`Weak`指针的`upgrade`方法实现,该方法返回一个类型为`Option><Rc<T>>`的值。
`Weak` 非常类似于 `Rc`,但是与 `Rc` 持有所有权不同,`Weak` 不持有所有权,它仅仅保存一份指向数据的弱引用:如果你想要访问数据,需要通过 `Weak` 指针的 `upgrade` 方法实现,该方法返回一个类型为 `Option><Rc<T>>` 的值。
看到这个返回,相信大家就懂了:何为弱引用?就是**不保证引用关系依然存在**,如果不存在,就返回一个`None`!
看到这个返回,相信大家就懂了:何为弱引用?就是**不保证引用关系依然存在**,如果不存在,就返回一个 `None`
因为`Weak`引用不计入所有权,因此它**无法阻止所引用的内存值被释放掉**, 而且`Weak`本身不对值的存在性做任何担保,引用的值还存在就返回`Some`,不存在就返回`None`。
因为 `Weak` 引用不计入所有权,因此它**无法阻止所引用的内存值被释放掉**,而且 `Weak` 本身不对值的存在性做任何担保,引用的值还存在就返回 `Some`,不存在就返回 `None`
#### Weak与Rc对比
我们来将`Weak`与`Rc`进行以下简单对比:
#### Weak Rc 对比
我们来将 `Weak``Rc` 进行以下简单对比:
| `Weak` | `Rc` |
|--------|-------------|

Loading…
Cancel
Save