Arc

使用不可变链表的一个原因是跨线程共享数据。毕竟,共享的可变状态是一切罪恶的根源,解决这个问题的一个方法就是永远消灭可变部分。

只是我们的列表根本不是线程安全的。为了保证线程安全,我们需要原子地修改引用计数。否则,两个线程可以尝试增加引用计数,并且只会发生一个。那么这个 list 很快就会被释放!

为了保证线程的安全性,我们必须使用 Arc。Arc 与 Rc 完全相同,只是引用计数是自动修改的。如果你不需要它,这有一点开销,所以 Rust 暴露了两者。我们需要做的就是用 std::sync::Arc 替换对 Rc 的所有引用。就是这样。我们是线安全的。成交!

但这提出了一个有趣的问题: 我们如何知道一个类型是否是线程安全的?我们会不会不小心搞砸了?

没有! 您不能在Rust中搞乱线程安全性!

之所以会出现这种情况,是因为 Rust 以一流的方式对线程安全性进行了建模,它有两个特性: Send 和 Sync。

如果可以安全地移动到另一个线程,则类型为Send。如果可以在多个线程之间安全地共享,则类型为Sync。也就是说,如果T是Sync,&T是Send。在这种情况下,Safe意味着不可能导致数据争用(不要误认为是更普遍的争用条件问题)。

这些是标记特征,这是一种表示它们是完全没有接口特征很好的方式。你要么被发送,要么不发送。这只是其他API可能需要的属性。如果您没有正确地发送,那么静态上不可能被发送到另一个线程!太好了!

发送和同步也是根据你是否完全由发送和同步类型组成而自动派生的特性。 这类似于仅由Copy类型构成时才可以实现Copy的方式,但是如果您愿意,我们将继续自动实现它。

几乎所有的类型都是发送和同步。一部分类型是发送,因为他们完全拥有自己的数据。一部分类型都是 Sync,因为跨线程共享数据的唯一方法是将它们放在共享引用后面,这使得它们是不可变!

然而,也有一些特殊类型违反了这些属性: 那些具有内部可变性的。到目前为止,我们只与继承的可变性(AKA 外部可变性)进行了真正的交互: 一个值的可变性是从其容器的可变性继承而来的。也就是说,您不能因为喜欢而随意变更不可变值的某个字段。

内部可变类型违反了这一点:它们允许您通过共享引用进行修改。内部可变性主要有两类:cells,它只在单线程上下文中工作;locks,在多线程上下文中工作。很明显,当你能用的时候,cells 更便宜。还有原子学,它们是起锁作用的原语。

那么,所有这些与Rc和Arc有什么关系? 好吧,他们都使用内部可变性作为参考计数。 更糟糕的是,此引用计数在每个实例之间共享! Rc仅使用一个cell ,这意味着它不是线程安全的。 Arc使用原子,这意味着它是线程安全的。 当然,您不能通过将其放在 Arc 中来神奇地使类型线程安全。 Arc只能像其他类型一样派生线程安全性。

我真的真的不想深入了解原子内存模型或非派生的 Send 实现的更细节。不用说,当你深入到 Rust 的线程安全故事,事情变得更加复杂。作为一个高层次的消费者,这一切都是可行的,你真的不需要去考虑它。

Last updated