Arc
使用不可变链表的一个原因是跨线程共享数据。毕竟,共享可变的状态是所有邪恶的根源, 解决这个问题的一个方法是永远杀死可变的部分。
只是我们的列表根本就不是线程安全的。为了实现线程安全,我们需要原子化地处理引用 计数。否则,两个线程可以尝试增加引用计数,但只有一个会发生。那么,列表就会过早 地被释放!
为了获得线程安全,我们必须使用Arc。Arc与Rc完全相同,只是引用计数被原子化地修改。
如果你不需要的话,这有一点开销,所以Rust将两者都暴露出来。我们需要做的就是用
std::sync::Arc
替换所有对Rc的引用,来制作我们的列表。就这样了。我们是线程安全
的。完成了!
但这提出了一个有趣的问题:我们如何知道一个类型是否是线程安全的?我们会不会不小心 搞砸了?
不会!在Rust中,你不可能搞乱线程安全。
之所以如此,是因为Rust通过两个特性以第一类的方式建立了线程安全模型。Send
和
Sync
。
如果一个类型可以安全地移动到另一个线程,那么它就是Send。如果一个类型在多个线程之
间共享是安全的,那么它就是Sync。也就是说,如果T
是Sync,&T
就是Send。在这种
情况下,安全意味着不可能引起数据竞争,(不要误解为更普遍的竞赛条件问题)。
这些是标记特性,这是一种华丽的说法,即它们是完全不提供接口的特性。你要么是Send, 要么不是。这只是一个其他API可以要求的属性。如果你不是合适的Send,那么就不可能被发 送到不同的线程中。很好!
Send和Sync也是基于你是否完全由Send和Sync类型组成的自动派生特性。这类似于如果你只由 Copy类型组成,你只能实现Copy,但如果你是Copy类型,我们就自动去实现它。
几乎每个类型都是Send和Sync。大多数类型都是Send,因为它们完全拥有自己的数据。大多数 类型是Sync,因为跨线程共享数据的唯一方法是把它们放在一个共享引用后面,这使得它们是 不可改变的
然而,有一些特殊的类型违反了这些属性:那些具有内部可变性的类型。到目前为止,我们只 与继承的可变性(又称外部可变性)进行了真正的互动:一个值的可变性是从其容器的可变性 中继承的。也就是说,你不能因为你喜欢而随机地改变一个不可变的值的某个字段。
内部可变性类型违反了这一点:它们让你通过一个共享的引用进行改变。内部可变性有两大类: 单元格,它只在单线程环境下工作;锁,它在多线程环境下工作。由于显而易见的原因,当你可 以使用单元格时,单元格开销更小。还有atomics,它是类似于锁的基元。
那么这一切与Rc和Arc有什么关系呢?好吧,它们都使用内部可变性来表示它们的引用计数。 更糟的是,这个引用计数在每个实例之间都是共享的!Rc只是使用一个单元,这意味着它不是线程 安全的。Arc使用了一个原子,这意味着它是线程安全的。当然,你不可能通过把一个类型放在 Arc中而神奇地使它成为线程安全的。Arc只能像其他类型一样派生出线程安全。
我真的真的不想讨论原子内存模型或非派生Send实现的更多细节。不用说,当你深入了解Rust的线 程安全故事时,事情会变得更加复杂。作为一个高级消费者,这一切都只是在工作,你真的不需 要考虑它。