3.4 Arc
2 分钟阅读
原文链接: https://rust-unofficial.github.io/too-many-lists/third-arc.html
用不可变链表的一个理由是在线程间共享数据。毕竟共享可变状态是万恶之源,一种解决办法是永远干掉可变部分。
但我们的链表根本不是线程安全的。要做到线程安全,得原子地修改引用计数。否则两个线程可能同时增加引用计数,只有一个会成功。链表就可能过早释放!
为此得用 Arc。Arc 和 Rc 完全一样,只是引用计数是原子修改的。不需要时有额外开销,所以 Rust 两者都暴露。让链表线程安全只需把所有 Rc 换成 std::sync::Arc。就这样。线程安全。完事!
但这引出一个有趣问题:怎么知道类型是否线程安全?会不小心搞砸吗?
不会!在 Rust 里你搞砸不了线程安全!
原因是 Rust 用两个 trait 以一等公民方式建模线程安全:Send 和 Sync。
类型是 Send 当可以安全地移动到另一线程。类型是 Sync 当可以安全地在多线程间共享。即若 T 是 Sync,&T 就是 Send。这里的安全指不可能造成数据竞争(不要和更一般的竞态条件混淆)。
这些是标记 trait,换句话说它们不提供任何接口。你要么是 Send,要么不是。只是其他 API 可以要求的属性。若不够 Send,静态上就不可能发到别的线程!爽!
Send 和 Sync 也是自动派生 trait,取决于你是否完全由 Send 和 Sync 类型组成。类似只有全 Copy 才能实现 Copy,但这里若满足就自动实现。
几乎所有类型都是 Send 和 Sync。大多数 Send 因为完全拥有自己的数据。大多数 Sync 因为跨线程共享数据的唯一方式是通过共享引用,那会让它们不可变!
但有些特殊类型违反这些性质:具有内部可变性的。到目前为止我们主要接触继承可变性(又称外部可变性):值的可变性继承自容器的可变性。不能随意变异不可变值的某个字段。
内部可变性类型违反这一点:允许通过共享引用变异。两大类:cell,只在单线程上下文工作;锁,多线程上下文工作。显然能用 cell 时更便宜。还有原子类型,像锁一样的原语。
这和 Rc、Arc 有什么关系?它们都用内部可变性管理引用计数。更糟的是,这个引用计数在所有实例间共享!Rc 用 cell,所以不是线程安全的。Arc 用 atomic,所以是线程安全的。当然,不能把类型放进 Arc 就让它自动变得线程安全。Arc 和其他类型一样只能派生线程安全性。
我真的真的真的不想深入原子内存模型或非派生 Send 实现的细节。不用说,越深入 Rust 的线程安全故事越复杂。作为高层使用者,一切就是能用,你不太需要想这些。