通过 Sync
和 Send
特性实现可扩展的并发
有趣的是,Rust 语言的并发特性 非常 少。到目前为止我们在本章中讨论的几乎所有并发特性都是标准库的一部分,而不是语言本身。处理并发的选项不仅限于语言或标准库;您可以编写自己的并发特性或使用他人编写的特性。
然而,两种并发概念嵌入在语言中:std::marker
特性 Sync
和 Send
。
允许使用 Send
在线程间转移所有权
Send
标记特征表示实现了 Send
的类型的值的所有权可以在线程之间转移。几乎所有 Rust 类型都是 Send
,但有一些例外,包括 Rc<T>
:这不能是 Send
,因为如果你克隆了一个 Rc<T>
值并尝试将克隆的所有权转移到另一个线程,两个线程可能会同时更新引用计数。因此,Rc<T>
被实现用于单线程情况,这样你就不必承担线程安全的性能开销。
因此,Rust 的类型系统和特征边界确保你永远不会意外地将 Rc<T>
值不安全地跨线程发送。当我们在清单 16-14 中尝试这样做时,我们得到了错误 the trait Send is not implemented for Rc<Mutex<i32>>
。当我们切换到 Arc<T>
,它是 Send
的,代码编译成功。
任何完全由 Send
类型组成的类型也会自动标记为 Send
。几乎所有原始类型都是 Send
,除了原始指针,我们将在第 20 章讨论。
允许多线程访问的 Sync
Sync
标记特征表明实现 Sync
的类型可以安全地从多个线程引用。换句话说,任何类型 T
如果 &T
(对 T
的不可变引用)是 Send
,即该引用可以安全地发送到另一个线程,则该类型 T
是 Sync
。类似于 Send
,原始类型是 Sync
,并且完全由 Sync
类型组成的类型也是 Sync
。
智能指针 Rc<T>
也不是 Sync
,原因与它不是 Send
的原因相同。我们在第 15 章讨论过的 RefCell<T>
类型及其相关的 Cell<T>
类型系列都不是 Sync
。RefCell<T>
在运行时执行的借用检查不是线程安全的。Mutex<T>
智能指针是 Sync
,可以用于在多个线程之间共享访问,如你在 “在多个线程之间共享 Mutex<T>
” 部分所见。
手动实现 Send
和 Sync
是不安全的
因为由 Send
和 Sync
特性组成的类型会自动也是 Send
和 Sync
,所以我们不必手动实现这些特性。作为标记特性,它们甚至没有任何方法需要实现。它们只是有助于强制执行与并发相关的不变性。
手动实现这些特质涉及到实现不安全的 Rust 代码。
我们将在第 20 章讨论使用不安全的 Rust 代码;目前,重要的是构建新的并发类型,这些类型不由 Send
和 Sync
组成,需要仔细考虑以维护安全保证。 “Rustonomicon” 提供了更多关于这些保证及其维护方法的信息。
摘要
这不会是你在这本书中最后一次看到并发:下一整章将专注于异步编程,第21章的项目将在更现实的情况下使用本章的概念,而不仅仅是这里讨论的小例子。
正如前面提到的,因为 Rust 处理并发的方式中很少部分是语言本身的一部分,许多并发解决方案都是以 crates 的形式实现的。这些 crates 比标准库进化得更快,所以请务必在线搜索当前最先进的 crates,以便在多线程情况下使用。
Rust 标准库提供了用于消息传递的通道和智能指针类型,如 Mutex<T>
和 Arc<T>
,这些类型在并发上下文中使用是安全的。类型系统和借用检查器确保使用这些解决方案的代码不会出现数据竞争或无效引用。一旦你的代码编译通过,你就可以放心地知道它将在多个线程上愉快地运行,而不会出现其他语言中常见的难以追踪的错误。并发编程不再是令人害怕的概念:大胆地让你的程序并发起来,无所畏惧!