Skip to content

28.1 什么是 Send

根据定义:如果类型 T 实现了 Send trait,那说明这个类型的变量在不同线程中传递所有权是安全的。但这句话对于初学者并不是那么容易理解的。 究竟具备什么特点的类型才满足 Send 约束?本节就来详细分析一下。

如果一个类型可以安全地从一个线程 move 进入另一个线程,那它就是 Send 类型。 比如:普通的数字类型是 Send,因为我们把数字 move 进入另一个线程之后,两个线程同时执行也不会造成什么安全问题。

更进一步,内部不包含引用的类型,都是 Send。因为这样的类型跟外界没有什么关联,当它被 move 进入另一个线程之后,它所有的部分都跟原来的线程没什么关系了,不会出现并发访问的情况。比如 String 类型。

稍微复杂一点的,具有泛型参数的类型,是否满足 Send 大多是取决于参数类型是否满足 Send。 比如Vec<T>,只要我们能保证T: Send,那么Vec<T>肯定也是 Send,把它 move 进入其他线程是没什么问题的。 再比如Cell<T>RefCell<T>Option<T>Box<T>,也都是这种情况。

还有一些类型,不论泛型参数是否满足 Send,都是满足 Send 的。这种类型,可以看作一种“构造器”,把不满足 Send 条件的类型用它包起来,就变成了满足 Send 条件的类型。 比如Mutex<T>就是这种。Mutex<T>这个类型实际上不关心它内部类型是怎样的,反正要访问内部数据,一定要调用lock()方法上锁,它的所有权在哪个线程中并不重要,所以把它 move 到其他线程也是没有问题的。

那么什么样的类型是!Send呢?典型的如Rc<T>类型。我们知道,Rc 是引用计数指针,把 Rc 类型的变量 move 进入另外一个线程,只是其中一个引用计数指针 move 到了其他线程,这样会导致不同的线程中的 Rc 变量引用同一块数据,Rc 内部实现没有做任何线程同步处理,这是肯定有问题的。 所以标准库中早已指定 Rc 是!Send。当我们试图在线程边界传递这个类型的时候,就会出现编译错误。

但是相对的是,Arc<T>类型是符合 Send 的(当然需要T: Send)。 为什么呢?因为 Arc 类型内部的引用计数用的是“原子计数”,对它进行增减操作,不会出现多线程数据竞争。所以,多个线程拥有指向同一个变量的 Arc 指针是可以接受的。

Released under the MIT License