Rust Send和Sync
不是所有类型都遵守可变性的原则。有一些类型允许你拥有一块内存的多个别名,同时还改变内存的值。除非这些类型使用同步来控制访问,否则它们就不是线程安全的。Rust根据Send
和Sync
这两个trait获取相关信息。
- 如果一个类型可以安全地传递给另一个线程,这个类型是
Send
- 如果一个类型可以安全地被多个线程共享(也就是
&T
是Send
),这个类型是Sync
Send
和Sync
是Rust并发机制的基础。因此,Rust赋予它们许多的特性,以保证它们能正确工作。首当其冲的,它们都是非安全trait。这表明它们的实现也是非安全的,而其他的非安全代码则可以假设这些实现是正确的。由于它们是标志trait(它们没有任何关联的方法),“正确地实现”仅仅意味着实现满足它所需要的内部特征。不正确地实现Send
和Sync
会导致未定义行为。
Send
和Sync
还是自动推导的trait。和其他的trait不同,如果一个类型完全由Send
或Sync
组成,那么这个类型本身也是Send
或Sync
。几乎所有的基本类型都是Send
和Sync
,因此你能见到的很多类型也就都是Send
和Sync
。
主要的例外情况有:
- 裸指针不是
Send
也不是Sync
(因为它们没有安全性保证) UnsafeCell
不是Sync
(所以Cell
和RefCell
也不是)Rc
不是Send
或Sync
(因为引用计数是共享且非同步的)
Rc
和UnsafeCell
是典型的非线程安全的:它们允许非同步地共享可变状态。可是,裸指针严格来说并不一定非得是非线程安全不可。通过裸指针做任何有意义的事情都需要先对它解引用,这一步就已经是非安全的了。从这个角度来说,有人可能会认为把它标为线程安全的也未尝不可。
可是,它们被标为非线程安全的主要目的是避免包含它们的类型自动成为线程安全的。这些类型都有着重要的不可追踪的所有权,保证它们线程安全需要花费大量的精力,而他们的作者不太可能做到这一点。Rc
就是一个很好的例子,一个包含*mut
的类型绝对不能是线程安全的。
不是自动推导的类型也可以很容易地实现Send
和Sync
:
struct MyBox(*mut u8); unsafe impl Send for MyBox {} unsafe impl Sync for MyBox {}
还有一个很少见的场景,一个类型被自动推导为Send
或Sync
,但是它其实不满足二者的要求。这是我们可以去掉Send
和Sync
的实现:
#![feature(option_builtin_traits)] // 我对于同步的基础类型有着神奇的语义 struct SpecialThreadToken(u8); impl !send for SpecialThreadToken {} impl !Sync for SpecialThreadToken {}
注意,一个类型自己不可能被不正确地推导为Send
和Sync
。只有当类型和其他的非安全代码一起实现了一些特殊行为时,它才可能成为一个不正确的Send
或Sync
。
大部分使用裸指针的类型都应该把裸指针用一种抽象隐藏起来,以保证类型可以被推导为Send
和Sync
。比如,所有Rust的标准集合类型都是Send
和Sync
(在他们包含Send
和Sync
类型的情况下),虽然它们都大量使用了裸指针处理内存分配和复杂的所有权。类似的,大部分这些集合的迭代器也是Send
和Sync
,因为它们的行为很像这些集合的&
或者&mut
。
Rust几乎全部抄袭了C11关于原子操作的内存模型。这么做并不是因为这个模型多么的优秀或者易于理解。事实上,这个模型非常的复杂,而且有一些已知的缺陷。不过,所有的原子操作模型其实都不怎么样,我们不得不因此做出一些妥协。至 ...