使用SyncSend trait 的可扩展并发

ch16-04-extensible-concurrency-sync-and-send.md
commit 55b294f20fc846a13a9be623bf322d8b364cee77

Rust 的并发模型中一个有趣的方面是语言本身对并发知道的很少。我们讨论过的几乎所有内容都是标准库的一部分,而不是语言本身的内容。因为并不需要语言提供任何用于并发上下文中的内容,并发选择也不仅限于标准库或语言所提供的:我们可以编写自己的或使用别人编写的内容。

我们说了几乎所有内容都不在语言本身,那么位于语言本身的是什么呢?这是两个 trait,都位于std::markerSyncSend

Send用于表明所有权可能被传送给其他线程

Send标记 trait 表明类型的所有权可能被在线程间传递。几乎所有的 Rust 类型都是Send的,不过有一些例外。标准库中提供的一个不是Send的类型是Rc<T>:如果克隆Rc<T>值并尝试将克隆的所有权传递给另一个线程,这两个线程可能会同时更新引用计数。正如上一部分提到的,Rc<T>被实现为用于单线程场景,这时不需要为拥有线程安全的引用计数而付出性能代价。

因为Rc<T>没有标记为Send,Rust 的类型系统和 trait bound 会确保我们永远也不会忘记或错误的把一个Rc<T>值不安全的在线程间传递。列表 16-14 曾尝试这么做,不过得到了一个错误说the trait Send is not implemented for Rc<Mutex<i32>>。当切换为标记为SendArc<T>时,代码就可以编译了。

任何完全由Send的类型组成的类型也会自动被标记为Send。几乎所有基本类型都是Send的,除了第十九章将会讨论的裸指针(raw pointer)之外。大部分标准库类型是Send的,除了Rc<T>之外。

Sync表明多线程访问是安全的

Sync标记 trait 表明一个类型可以安全的在多个线程中拥有其值的引用。换一种方式来说就是对于任意类型T,如果&TT的引用)是Send的话T就是Sync的,这样其引用就可以安全的发送到另一个线程。类似于Send的情况,基本类型是Sync的,完全由Sync的类型组成的类型也是Sync的。

Rc<T>也不是Sync的,出于其不是Send的相同的原因。RefCell<T>(第十五章讨论过)和Cell<T>系列类型不是Sync的。RefCell<T>在运行时所进行的借用检查也不是线程安全的。Mutex<T>Sync的,正如上一部分所讲的它可以被用来在多线程中共享访问。

手动实现SendSync是不安全的

通常并不需要实现SendSync trait,因为由是SendSync的类型组成的类型也自动就是SendSync的了。因为他们是标记 trait,甚至都不需要实现任何方法。他们只是用来加强并发相关的不可变行性的。

实现这些标记 trait 涉及到实现不安全的 Rust 代码。第十九章将会讲到如何使用不安全 Rust 代码;现在,重要的是在创建新的由不是SendSync的部分构成的并发类型时需要多加小心,以确保维持其安全保证。The Nomicon 中由更多关于这些保证和如何维持他们的信息。

总结

这不会是本书最后一个出现并发的章节;第二十章的项目会在更现实的场景中使用这些概念,而不像本章中讨论的这些小例子。

正如我们提到的,因为 Rust 本身很少有处理并发的部分内容,有很多的并发方案都由 crate 实现。他们比标准库要发展的更快;请在网上搜索当前最新的用于多线程场景的 crate。

Rust 提供了用于消息传递的通道,和像Mutex<T>Arc<T>这样可以安全的用于并发上下文的智能指针。类型系统和借用检查器会确保这些场景中的代码不会出现数据竞争和无效的引用。一旦代码可以编译了,我们就可以坚信这些代码可以正确的运行于多线程环境,而不会出现其他语言中经常出现的那些难以追踪的 bug。并发编程不再是什么可怕的概念:无所畏惧的使你的程序使用并发吧!

接下来,让我们讨论一下当 Rust 程序变得更大时那些符合习惯的模拟问题和结构的解决方案,以及 Rust 风格如何与面向对象编程(Object Oriented Programming)中那些你所熟悉的概念相联系。