Rust Send、Sync trait理解
Rust的异步并发编程中,大量的接口参数、返回值要求类型为Send、Sync,那这2个trait到底是什么意思,有什么区别,为什么在异步编程中需要他们,以及类型是如何实现这些trait的;本文会从上面几个问题入手带你深入了解这2个trait.
Send、Sync 语义
Sync
:1)行为上:允许一个对象在多个线程中被同时使用;2)原理上:将一个实现了Sync类型的不可变引用安全的传递给另一个线程,即只有实现了Sync Trait的类型才可以安全地被多个线程引用。
- 但对于可变对象如果想要是Sync的,就需要用同步原语进行多线程同步(如: Mutex, RwMutex)将这些非Sync对象变为Sync对象,另一种机制就是使用原子类型进行处理。
- 对于任何类型T,如果&T(也就是T的引用)满足约束Send,那么T类型就是实现了Sync的类型,因为它的引用可以安全的发送给其它线程,允许多个线程在同一时间通过引用安全的使用同一对象。
Send
: 1) 行为上:允许一个对象在多个线程中在不同时间被使用, 2) 原理上:允许对象在线程间转移所有权,只有实现了Send trait的类型才可以在线程间转移所有权
- 如:线程A创建、并使用一个对象一会,然后再将这个对象发送给线程B,这时线程B就可以使用这个对象而线程 A则无法使用。Rust的ownership语义保证了这个对象不会在2个线程中被同时使用、从而保证线程的Send对象的线程安全。
区别
从上面的解释可以看到,2者重要的区别是:行为上:多线程中同一个对象的使用方式,Sync属性的对象可以在多线程中被同时使用,而Send属性的对象同一时间只能有一个线程使用。原理上:一个是按值传递涉及所有权的转移,另一个是按引用传递.
Rust中大多数类型既实现了Send也实现了Sync,甚至不必为自定义的结构体/枚举手动实现这些trait,Rust会自动帮你实现;如果结构体或枚举的所有字段都是Send那它也是Send,同理Sync。
但Rust中有些类型刻意设计为Send但非Sync的,如:mpsc::Receiver类型,保证了mpsc通道的接收端一次只能被一个线程使用。
少数既不是Send也 不是Sync的类型大多数使用了非线程安全的内部可变性,如引用计数器指针:std::rc::Rc<T>
;
- 如果
Rc<String>
是Sync的,那么在线程间通过共享引用共享单个Rc时就会出发数据竞争,如:2个线程同时增加共享引用计数会导致引用技术结果变得不准确。
在异步编程中对于跨线程边界使用的函数,Send、Sync会作为函数类型签名中的约束。当spawn一个线程时传入的闭包必须实现了Send trait,意味着它包含的所有值都必须是Send;同样如果通过通道将值发送到另一个线程,该值也必须是Send.
参考
Rust 程序设计(第2版)
Rust 权威指南