键盘快捷键

按 ← 或 → 在章节之间导航

按 S 或 / 在书中搜索

按 ? 显示此帮助

按 Esc 隐藏此帮助

使用 Send 和 Sync 实现可扩展并发

有趣的是,本章到目前为止我们讨论的几乎每一个并发 特性都是标准库的一部分,而不是语言本身。你处理 并发的选择并不局限于语言或标准库;你可以编写 自己的并发特性,或使用他人编写的并发特性。

然而,在嵌入语言本身(而非标准库)的关键并发 概念中,就有 std::marker 中的 Send 和 Sync trait。

在线程之间转移所有权

Send 标记 trait 表明实现了 Send 的类型的值的所有权 可以在线程之间转移。几乎所有 Rust 类型都实现了 Send,但有一些例外,包括 Rc<T>:它不能 实现 Send,因为如果你克隆了一个 Rc<T> 值并试图 将克隆的所有权转移到另一个线程,两个线程就可能同时 更新引用计数。因此,Rc<T> 被实现为用于 单线程场景,在这种场景下你不需要付出 线程安全的性能代价。

因此,Rust 的类型系统和 trait 约束确保你永远不会 意外地跨线程不安全地发送 Rc<T> 值。当我们在示例 16-14 中尝试这样做时,得到了错误 the trait `Send` is not implemented for `Rc<Mutex<i32>>`。当我们改用实现了 Send 的 Arc<T> 时,代码就编译通过了。

任何完全由 Send 类型组成的类型也会自动被标记为 Send。几乎所有原始类型都是 Send,除了原始指针,我们 将在第 20 章讨论它们。

从多个线程访问

Sync 标记 trait 表明实现了 Sync 的类型 可以安全地从多个线程中被引用。换句话说,任何类型 T 在 &T(T 的不可变引用)实现了 Send 时就实现了 Sync, 这意味着该引用可以被安全地发送到另一个线程。与 Send 类似, 原始类型都实现了 Sync,完全由实现了 Sync 的类型组成的类型也实现了 Sync。

智能指针 Rc<T> 出于与其未实现 Send 相同的原因 也未实现 Sync。RefCell<T> 类型(我们在第 15 章讨论过) 以及相关的 Cell<T> 类型家族都未实现 Sync。RefCell<T> 在运行时进行的借用检查实现 不是线程安全的。智能指针 Mutex<T> 实现了 Sync,可以 用来与多个线程共享访问,正如你在“对 Mutex<T> 的共享访问”一节中所看到的。

手动实现 Send 和 Sync 是不安全的

由于完全由其他实现了 Send 和 Sync trait 的类型组成的类型也会自动实现 Send 和 Sync,所以我们不必手动实现这些 trait。作为标记 trait,它们甚至没有任何方法需要实现。它们只是 用于强制实施与并发相关的不变量。

手动实现这些 trait 涉及实现不安全的 Rust 代码。 我们将在第 20 章讨论使用不安全的 Rust 代码;目前,重要的 信息是构建不由 Send 和 Sync 部分组成的新的并发类型需要谨慎思考,以维护 安全保证。《Rust 秘典》(The Rustonomicon)提供了有关这些保证以及如何维护 它们的更多信息。

总结

这不是你在本书中最后一次看到并发:下一章 将专注于异步编程,第 21 章的项目将在比这里 讨论的小型示例更贴近实际的场景中运用本章的 概念。

如前所述,由于 Rust 处理并发的方式很少属于 语言本身,许多并发解决方案是以 crate 形式实现的。 它们比标准库演进得更快,所以请务必 在线搜索当前最先进的、可在多线程 场景中使用的 crate。

Rust 标准库提供了用于消息传递的通道,以及 诸如 Mutex<T> 和 Arc<T> 等可安全用于并发环境的智能 指针类型。类型系统和借用检查器确保 使用这些方案的代码不会以数据竞争或无效引用告终。 一旦你让代码编译通过,就可以放心它能在 多个线程上愉快地运行,而不会出现其他语言中常见的 难以追踪的 bug。并发编程不再是令人畏惧的 概念:放手去做,让你的程序无畏地并发吧!