前言 Preface
Rust 在让系统编程变得更加可及方面已经发挥了、并且仍在持续发挥着重要作用。然而,诸如原子操作(atomics)和内存顺序(memory ordering)这样的底层并发主题,至今仍常被视为带有某种神秘色彩的领域,似乎只适合交由极少数专家来处理。
在过去几年中,我一边从事基于 Rust 的实时控制系统开发,一边参与 Rust 标准库的工作。我逐渐发现,现有关于原子操作及相关主题的资料,往往只覆盖了我所需要信息的一小部分。许多资源完全聚焦于 C 和 C++,这使得它们与 Rust 的(内存与线程)安全理念以及类型系统之间的联系并不直观。还有一些资源虽然深入讲解了抽象层面的理论,例如 C++ 的内存模型,但却很少、甚至完全没有说明这些理论与实际硬件之间的关系。与此同时,也有大量资料详尽描述了底层硬件的各个细节,例如处理器指令和缓存一致性,但若想形成一个整体性的理解,往往需要从许多不同来源中零散地拼凑信息。
本书正是一次将这些相关信息汇聚到同一处的尝试:把它们彼此连接起来,提供构建正确、安全且符合人体工程学的并发原语所需的一切知识,同时帮助你对底层硬件以及操作系统所扮演的角色建立足够深入的理解,从而能够在设计时做出合理的决策,并进行基本的性能与复杂度权衡。
本书适合哪些读者
本书的主要读者群体是希望深入了解底层并发机制的 Rust 开发者。此外,对于那些尚未非常熟悉 Rust,但希望从 Rust 的视角了解底层并发是如何运作的读者来说,本书同样适合。
本书假设你已经掌握 Rust 的基础知识,安装了较新的 Rust 编译器,并且知道如何使用 cargo 编译和运行 Rust 代码。与并发相关的重要 Rust 概念会在需要时进行简要说明,因此不要求你事先了解 Rust 的并发编程。
章节概览
本书共包含十章。以下是每一章的内容简介,以及你可以期待从中学到的内容:
第 1 章 —— Rust 并发基础
本章介绍 Rust 中进行基础并发编程所需的全部工具和概念,例如线程、互斥锁、线程安全、共享与独占引用、内部可变性等。这些内容构成了全书后续章节的基础。
对于已经熟悉这些概念的资深 Rust 程序员来说,本章可以作为一次快速回顾;而对于在其他语言中接触过这些概念、但尚不熟悉 Rust 的读者来说,本章将迅速补齐后续章节所需的 Rust 特有知识。
第 2 章 —— 原子操作(Atomics)
在第二章中,我们将学习 Rust 提供的原子类型及其所有操作。从最基本的加载(load)和存储(store)操作开始,逐步深入到更高级的 compare-and-exchange 循环,并通过多个真实世界的使用场景来讲解每一个新概念。
尽管内存顺序对每一次原子操作都至关重要,但该主题将留到下一章讨论。本章只覆盖使用宽松内存顺序(relaxed ordering)即可满足需求的场景——而这种情况其实比你想象中要常见得多。
第 3 章 —— 内存顺序(Memory Ordering)
在学习了各种原子操作及其用法之后,第三章将介绍本书中最复杂的主题:内存顺序。
我们将深入探讨内存模型的工作方式、什么是 happens-before 关系以及如何建立它们、各种内存顺序的含义,以及为什么顺序一致性(sequential consistency)并非解决所有问题的万能答案。
第 4 章 —— 构建我们自己的自旋锁(Spin Lock)
在掌握理论之后,接下来的三章将把理论付诸实践,通过从零实现多种常见的并发原语来加深理解。本章是其中的第一章,也是较短的一章,我们将实现一个自旋锁。
我们会从一个极简版本开始,用它来实践 release 和 acquire 内存顺序,然后结合 Rust 的安全性理念,将其逐步打磨成一个易用且不易被误用的 Rust 数据类型。
第 5 章 —— 构建我们自己的通道(Channel)
在第 5 章中,我们将从零开始实现多种“一次性通道”(one-shot channel)的变体,这是一种用于在线程之间传递数据的并发原语。
我们会从一个极其简洁、但完全不安全的实现开始,逐步探索多种设计安全接口的方法,同时讨论不同设计决策及其带来的影响。
第 6 章 —— 构建我们自己的 “Arc”
第六章将面对一个更具挑战性的内存顺序难题:从零实现原子引用计数。
在为其添加弱引用支持并进行性能优化之后,我们的最终版本将在行为和性能上都与 Rust 标准库中的 std::sync::Arc 几乎完全一致。
第 7 章 —— 理解处理器(Processor)
第七章将深入探讨底层细节。我们会研究处理器层面究竟发生了什么,原子操作在两种最常见处理器架构上对应的汇编指令是什么,缓存的工作原理及其对性能的影响,以及在硬件层面上,内存模型究竟还剩下什么。
第 8 章 —— 操作系统原语
在第 8 章中,我们将认识到:有些事情离不开操作系统内核的支持,并学习 Linux、macOS 和 Windows 提供了哪些相关功能。
我们将讨论 POSIX 系统中通过 pthreads 提供的并发原语,了解 Windows API 能做什么,以及深入理解 Linux 的 futex 系统调用。
第 9 章 —— 构建我们自己的锁(Locks)
基于前几章学到的知识,第 9 章将从零实现多种互斥锁、条件变量以及读写锁。
对于每一种原语,我们都会先实现一个最小但完整的版本,然后尝试用不同方式对其进行优化。通过一些简单的基准测试,我们将发现:优化尝试并不总是能带来性能提升,并在过程中讨论各种设计取舍。
第 10 章 —— 思路与灵感
最后一章确保你在读完整本书后不会陷入“无从下手”的状态,而是带着新的想法与灵感离开,去构建和探索更多有趣的项目,或许就此开启一段更深入的底层并发之旅。
代码示例
本书中的所有代码均基于 Rust 1.66.0 编写并测试,该版本发布于 2022 年 12 月 15 日。更早的 Rust 版本不包含本书所使用的全部特性,而更新的版本则应当可以正常运行这些代码。
为简洁起见,代码示例中通常不会包含 use 语句,除非是第一次引入某个新的标准库条目。为了方便编译本书中的任意代码示例,你可以使用以下 prelude 一次性导入所需的全部内容:
#[allow(unused)]
use std::{
cell::{Cell, RefCell, UnsafeCell},
collections::VecDeque,
marker::PhantomData,
mem::{ManuallyDrop, MaybeUninit},
ops::{Deref, DerefMut},
ptr::NonNull,
rc::Rc,
sync::{*, atomic::{*, Ordering::*}},
thread::{self, Thread},
};
补充材料(包括所有代码示例的完整版本)可在以下网站获取: https://marabos.nl/atomics/
你可以将本书中提供的所有示例代码用于任何用途。欢迎署名,但并非强制要求。署名通常包括书名、作者、出版社和 ISBN,例如:
“Rust Atomics and Locks by Mara Bos (O’Reilly). Copyright 2023 Mara Bos, 978-1-098-11944-7.”
本书使用的排版约定
本书采用以下排版约定:
- 斜体:用于新术语、URL 以及强调内容
- 等宽字体:用于程序代码,以及在正文中引用程序元素(如变量名、函数名、数据类型、语句和关键字)
联系方式
O’Reilly 为本书提供了专门的网页,用于列出勘误、示例以及其他补充信息: https://oreil.ly/rust-atomics-and-locks
如需对本书内容发表评论或提出技术问题,请发送邮件至: bookquestions@oreilly.com
如果你希望复用本书中的内容,且你的使用方式超出了合理使用或本前言中授予的权限范围,请联系: permissions@oreilly.com
有关 O’Reilly 的最新消息,请访问: https://oreilly.com
关注 O’Reilly 的 Twitter: https://twitter.com/oreillymedia 关注作者的 Twitter: https://twitter.com/m_ou_se
致谢
我要感谢所有参与本书创作的人。许多人提供了支持和宝贵的意见,这对本书的完成起到了极大的帮助。尤其要感谢 Amanieu d’Antras、Aria Beingessner、Paul McKenney、Carol Nichols 以及 Miguel Raz Guzmán Macedo,他们为早期草稿提供了极其宝贵而深刻的反馈。同时也要感谢 O’Reilly 的所有工作人员,特别是我的编辑 Shira Evans 和 Zan McQuade,感谢他们始终如一的热情与支持。