|
|
|
@ -207,7 +207,7 @@ error: aborting due to 2 previous errors
|
|
|
|
|
|
|
|
|
|
<span class="filename">Filename: src/main.rs</span>
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
|
```rust
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
use std::sync::Mutex;
|
|
|
|
|
use std::thread;
|
|
|
|
@ -258,15 +258,15 @@ std::marker::Send` is not satisfied
|
|
|
|
|
|
|
|
|
|
哇哦,太长不看!说重点:第一个提示表明 `Rc<Mutex<i32>>` 不能安全的在线程间传递。理由也在错误信息中,“不满足 `Send` trait bound”(`the trait bound Send is not satisfied`)。下一部分将会讨论 `Send`,它是确保许多用在多线程中的类型,能够适合并发环境的 trait 之一。
|
|
|
|
|
|
|
|
|
|
不幸的是,`Rc<T>` 并不能安全的在线程间共享。当 `Rc<T>` 管理引用计数时,它必须在每一个 `clone` 调用时增加计数,并在每一个克隆被丢弃时减少计数。`Rc<T>` 并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值。那么如果有一个正好与 `Rc<T>` 类似,不过以一种线程安全的方式改变引用计数的类型会怎么样呢?
|
|
|
|
|
不幸的是,`Rc<T>` 并不能安全的在线程间共享。当 `Rc<T>` 管理引用计数时,它必须在每一个 `clone` 调用时增加计数,并在每一个克隆被丢弃时减少计数。`Rc<T>` 并没有使用任何并发原语,来确保改变计数的操作不会被其他线程打断。在计数出错时可能会导致诡异的 bug,比如可能会造成内存泄漏,或在使用结束之前就丢弃一个值。那么如果有一个正好与 `Rc<T>` 类似,而又以一种线程安全的方式改变引用计数的类型会怎么样呢?
|
|
|
|
|
|
|
|
|
|
#### 原子引用计数 `Arc<T>`
|
|
|
|
|
|
|
|
|
|
如果你思考过像之前那样的问题的话,你就是正确的。确实有一个类似`Rc<T>`并可以安全的用于并发环境的类型:`Arc<T>`。字母“a”代表**原子性**(*atomic*),所以这是一个**原子引用计数**(*atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语;请查看标准库中`std::sync::atomic`的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。
|
|
|
|
|
如果你想过之前的问题,答案是肯定的,确实有一个类似`Rc<T>`并可以安全的用于并发环境的类型:`Arc<T>`。字母“a”代表**原子性**(*atomic*),所以这是一个**原子引用计数**(*atomically reference counted*)类型。原子性是另一类这里还未涉及到的并发原语;请查看标准库中`std::sync::atomic`的文档来获取更多细节。其中的要点就是:原子性类型工作起来类似原始类型,不过可以安全的在线程间共享。
|
|
|
|
|
|
|
|
|
|
那为什么不是所有的原始类型都是原子性的呢,然后为什么不是所有标准库中的类型都默认使用`Arc<T>`实现呢?线程安全伴随一些性能惩罚,我们只希望在需要时才为此付出代价。如果只是在单线程中对值进行操作,因为并不需要原子性提供的保证,所以代码可以运行的更快。
|
|
|
|
|
为什么不是所有的原始类型都是原子性的?为什么不是所有标准库中的类型都默认使用`Arc<T>`实现?线程安全带来性能惩罚,我们希望只在必要时才为此买单。如果只是在单线程中对值进行操作,原子性提供的保证并无必要,代码可以因此运行的更快。
|
|
|
|
|
|
|
|
|
|
回到之前的例子:`Arc<T>`和`Rc<T>`除了`Arc<T>`内部的原子性之外他们是等价的。其 API 也是一样的,所以可以修改`use`行和`new`调用。列表 16-15 中的代码最终可以编译和运行:
|
|
|
|
|
回到之前的例子:`Arc<T>`和`Rc<T>`除了`Arc<T>`内部的原子性之外没有区别。其 API 也相同,所以可以修改`use`行和`new`调用。列表 16-15 中的代码最终可以编译和运行:
|
|
|
|
|
|
|
|
|
|
<span class="filename">Filename: src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -311,6 +311,6 @@ Result: 10
|
|
|
|
|
|
|
|
|
|
回忆一下`Rc<T>`并没有避免所有可能的问题:我们也讨论了当两个`Rc<T>`相互引用时的引用循环的可能性,这可能造成内存泄露。`Mutex<T>`有一个类似的 Rust 同样也不能避免的问题:死锁。**死锁**(*deadlock*)是一个场景中操作需要锁定两个资源,而两个线程分别拥有一个锁并永远相互等待的问题。如果你对这个主题感兴趣,尝试编写一个带有死锁的 Rust 程序,接着研究任何其他语言中使用互斥器的死锁规避策略并尝试在 Rust 中实现他们。标准库中`Mutex<T>`和`MutexGuard`的 API 文档会提供有用的信息。
|
|
|
|
|
|
|
|
|
|
Rust 的类型系统和所有权(规则)确保了线程在更新共享值时拥有独占的访问权限,所以线程不会以一种不可预测的方式覆盖彼此的操作。虽然为了使一切正确运行而在编译器上花了一些时间,但是我们节省了未来可能需要重现只在线程以特定顺序执行时才会出现的诡异错误场景的时间。
|
|
|
|
|
Rust 的类型系统和所有权规则,确保了线程在更新共享值时拥有独占的访问权限,所以线程不会以不可预测的方式覆盖彼此的操作。虽然为了使一切正确运行而在编译器上花了一些时间,但是我们节省了未来的时间,尤其是线程以特定顺序执行才会出现的诡异错误难以重现。
|
|
|
|
|
|
|
|
|
|
接下来,为了丰富本章的内容,让我们讨论一下`Send`和`Sync` trait 以及如何对自定义类型使用他们。
|