Rust-Send-Sync
线程模型下,多个线程在同一时刻对同一个数据进行读写或者写写,会导致线程不安全。另外,与线程绑定的操作,在多线程中也会出现线程不安全的问题。
而 Send 和 Sync marker trait ,让多线程编程更直白明确。
Send 类型可以在线程间安全地被复制或着移动。Sync 类型可以在线程间安全地共享引用(&T)。&mut T 天然地被 Rust 借用规则限制,不可能两个线程同时拥有 &mut T。
Send
除 pointer 类型外,所有 primitives 类型都是 Send。
unsafe impl<T: Sync + ?Sized> Send for &T {}
表示如果 T 是 Sync,其不可变引用 &T 可以复制到其他线程被安全地使用
Sync
Rust 中的 内部可变性模式 (Cell/Atomic等),&T 并不能保证只读数据,因此 Sync 解决的问题就是 &T 跨线程的安全性。
UnsafeCell 不是 Sync,Rc 和 RefCell 也不是 Sync。而如果是拥有同步机制的实现,AtomicBool、Arc、Mutex 等,则可以是 Sync。
而如果 T 不是具有内部可变性的类型,&T 只读数据,因此 T 可以跨线程共享引用,已经满足 Sync。
除 pointer 类型外,所有 primitives 类型都是 Sync。基于 Sync 类型实现的 struct/enum 等也会自动实现 Sync。
Send 和 Sync 都是 auto marker
trait,编译器会自动给每个符合的类型自动实现该 trait,也可以手动通过
impl !Send for XXX
来显示地标识某类型不是 Send。
Send 与 Sync 的关联
T: Sync 等价于 &T: Send
T: Sync 等价于 &T: Sync:T 是 Sync,所以 &T 是 Send。&T 是 Sync 的前提是 &&T 为 Send,而 &&T 实际就是 &T(共享是可传递的),所以 &T 则为 Sync
T: Sync 等价于 &mut T: Sync:T 是 Sync,所以 &T 是 Send。&mut T 是 Sync 的前提是 &&mut T 为 Send,而 &&mut T 实际就是 &T,所以 &&mut T 也是 Send,那么 &mut T 则为 Sync
T: Send 等价于 &mut T: Send
&mut T 某种程度可以看作拥有 T 的所有权(只不过它不能将 T 完全 drop,需要保证引用在其生命周期内有效),因为 &mut T 可以通过
*ref = T
来覆写其整个数据(原数据被 drop),也可以通过 std::mem::replace 将其指向的原数据 move 走。所以 &mut T 是 Send 的前提是 T 必须可以在线程间安全地 move,即 T 为 Send。
Send 与 Sync 示例
Send + Sync
绝大多数 primitives 类型:bool、i32、array
Atomic 类型:AtomicBool、AtomicI32
基于 Send + Sync 类型构建的 struct、tuple、enum
Arc:
unsafe impl<T: ?Sized + Sync + Send, A: Allocator + Sync> Sync for Arc<T, A> {}
,unsafe impl<T: ?Sized + Sync + Send, A: Allocator + Send> Send for Arc<T, A> {}
因为 Arc 是共享所有权,因此不确定 T 最终会在哪个线程上被 drop,因此 Arc 是 Send 的前提之一是 T 是 Send
Arc 可以通过 Deref 拿到 &T,因此在线程间发送 Arc 等价于发送 &T,而 &T 可以被安全发送的前提是 T 必须是 Sync
在线程间发送 Arc 或其引用 &Arc 本质是一样的,因为两者都可以通过 clone 拿到一个新的 Arc
Mutex:
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
,unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
- 如果 T 不是 Send,那么 Mutex 被发送到其他线程后获取锁 MutexGuard,进而通过 DerefMut 获取到 &mut T,而 &mut T 可以将其原值 drop 或者 move,存在问题。因此 T 必须 Send,Mutex 才会是 Send
- 如果 T 不是 Send,那么 Mutex 的引用被共享到其他线程后仍能获取锁 MutexGuard,进而通过 DerefMut 获取到 &mut T,而 &mut T 可以将其原值 drop 或者 move,存在问题。因此 T 必须 Send,Mutex 才会是 Sync
RwLock:
unsafe impl<T: ?Sized + Send> Send for RwLock<T> {}
,unsafe impl<T: ?Sized + Send + Sync> Sync for RwLock<T> {}
- 如果 T 不是 Send,那么 RwLock 被发送到其他线程后获取写锁 RwLockWriteGuard,进而通过 DerefMut 获取到 &mut T,而 &mut T 可以将其原值 drop 或者 move,存在问题。因此 T 必须 Send,RwLock 才会是 Send
- 如果 T 不是 Send,那么 RwLock 的引用被共享到其他线程后仍能获取写锁 RwLockWriteGuard,进而通过 DerefMut 获取到 &mut T,而 &mut T 可以将其原值 drop 或者 move,存在问题。因此 T 必须 Send,RwLock 才会是 Sync
- 如果 T 不是 Sync,那么 RwLock 的引用被共享到其他线程后可能多个线程同时获取到读锁 RwLockReadGuard,进而通过 Deref 获取到 &T,而 T 不是 Sync 的话,那么多线程同时使用 &T 存在问题。因此 T 必须 Sync,RwLock 才会是 Sync
Send + !Sync
- Cell 和 RefCell,其拥有内部可变性,但并没有使用同步机制
!Send + Sync
- MutexGuard,必须在加锁的线程上去释放锁(发生在 drop 时),因此不能
Send 到其他线程上被 drop。MutexGuard 可以通过 DerefMut 获取 &mut
T,因此当 T 是 Sync 时,MutexGuard 才是
Sync(
impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T>
)
!Send + !Sync
- Rc,其内部引用计数非原子更新,Rc 和 &Rc 均无法发送到其他线程,因为两者都可以通过 clone 改变内部引用计数器
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!