在Rust的并发编程中,Sync和Send是两个重要的标准库特性(traits),它们界说了类型在多线程环境下的行为。Rust以其严格的全部权和借用系统确保内存安全,制止了传统并发编程中的很多常见问题。本文将详细解释Sync与Send的含义、利用场景以及常见的混淆点。
1. 什么是Send?
Send是一个标记trait,用于标识一个类型可以在线程之间通报。假如一个类型实现了Send,则可以通过全部权转移(而非引用共享)将其从一个线程通报到另一个线程。在Rust中,全部权转移时会自动处理内存管理,制止了传统并发模子中的数据竞争问题。
Send的关键点
- Send表示类型能够在线程之间通报,通常是通过全部权转移。
- 假如一个类型实现了Send,则可以安全地将其通报给另一个线程。
- 原生类型(如整数、浮点数、布尔值等)和大多数实现了Send的类型可以在多个线程间通报。
示例
- use std::thread;
- fn main() {
- let data = vec![1, 2, 3, 4, 5];
- let handle = thread::spawn(move || {
- println!("{:?}", data); // 数据被移动到新线程
- });
- handle.join().unwrap();
- }
复制代码 在上面的代码中,data是一个Vec<i32>,它实现了Send,因此可以安全地通报到新线程。这里的move关键字将data的全部权转移到新的线程中,而不会产生数据竞争。
非Send类型
某些类型不能实现Send,尤其是那些包含不可变引用或引用计数的类型。比方,Rc<T>(引用计数指针)和RefCell<T>(可变借用类型)都不能在多个线程之间通报,因为它们依赖于线程局部存储的引用计数或可变借用,这在多线程环境中是不安全的。
- use std::rc::Rc;
- use std::thread;
- fn main() {
- let rc = Rc::new(5);
- let handle = thread::spawn(move || {
- println!("{:?}", rc); // 编译错误,Rc不能被跨线程传递
- });
- handle.join().unwrap();
- }
复制代码 这段代码会编译失败,因为Rc不能在多个线程间通报。
2. 什么是Sync?
Sync是另一个标记trait,用来标识一个类型可以安全地在多个线程之间共享引用。换句话说,假如一个类型实现了Sync,那么它的引用可以在多个线程中共享而不会引发数据竞争。
Sync的关键点
- Sync表示类型的引用可以在多个线程间共享。
- 只有当类型的内部数据能够被多个线程安全地访问时,它才会实现Sync。
- 假如类型实现了Sync,则该类型的引用(如&T)可以在线程间共享。
示例
- use std::sync::{Arc, Mutex};
- use std::thread;
- fn main() {
- let data = Arc::new(Mutex::new(0));
- let mut handles = vec![];
- for _ in 0..10 {
- let data = Arc::clone(&data);
- let handle = thread::spawn(move || {
- let mut num = data.lock().unwrap();
- *num += 1;
- });
- handles.push(handle);
- }
- for handle in handles {
- handle.join().unwrap();
- }
- println!("Result: {}", *data.lock().unwrap());
- }
复制代码 在这段代码中,Arc和Mutex都实现了Sync,它们允很多个线程共享对同一数据的引用。Mutex确保同一时间只有一个线程可以访问数据,而Arc则使得可以在多个线程中共享对Mutex的全部权。
如何明白Sync
- Sync类型可以在多个线程之间共享,但数据的访问可能需要额外的同步机制(如Mutex或RwLock)。
- Sync不意味着类型自己提供内置的线程安全行为,而是指它的引用可以安全地被多个线程同时访问。
3. Send与Sync的关系
Send与Sync的常见误解
- Send与Sync常常一起利用:很多类型实现了Send,也会实现Sync。比方,Arc<T>和Mutex<T>都实现了Sync,并且它们在多个线程之间通报时也能保证线程安全。
- Send和Sync并不是一回事:Send关注的是如安在多个线程间通报数据(即全部权转移),而Sync关注的是如安在多个线程间共享数据(即引用共享)。
一些容易混淆的例子
Vec<T>是Send但不是Sync
Vec<T>是一个常见的动态数组类型。它实现了Send,因为你可以将Vec<T>的全部权转移到另一个线程,但它并不实现Sync,因为Vec<T>的引用不能在多个线程之间共享。
- use std::thread;
- fn main() {
- let vec = vec![1, 2, 3, 4, 5];
- let handle = thread::spawn(move || {
- println!("{:?}", vec); // vec 被移动到新的线程
- });
- handle.join().unwrap();
- }
复制代码 在这段代码中,Vec<T>的全部权被安全地通报到新线程。但是,Vec<T>自己不能在多个线程中共享引用,因为它并没有提供任何同步机制来保证数据的同等性。
Mutex<T>是Sync但不是Send
Mutex<T>是一个提供互斥访问的数据结构,它允许一个线程锁定并修改数据,但它自己并不总是Send,特殊是当它内部存储的数据类型不是Send时。
- use std::sync::Mutex;
- use std::rc::Rc;
- use std::thread;
- fn main() {
- let data = Rc::new(5);
- let mutex = Mutex::new(data);
- let handle = thread::spawn(move || {
- let _locked_data = mutex.lock().unwrap(); // 编译错误,因为Rc不能被跨线程传递
- });
- handle.join().unwrap();
- }
复制代码 由于Rc<T>不是Send的,所以Mutex<Rc<T>>也不能在多个线程之间通报。
Arc<T>是Send和Sync
Arc<T>(原子引用计数)是一个线程安全的智能指针,它允许在多个线程之间共享全部权。Arc<T>实现了Send和Sync,因此它的引用可以在多个线程中安全地共享和通报。
- use std::sync::Arc;
- use std::thread;
- fn main() {
- let data = Arc::new(5);
- let handles: Vec<_> = (0..10).map(|_| {
- let data = Arc::clone(&data);
- thread::spawn(move || {
- println!("{}", data); // 共享引用
- })
- }).collect();
- for handle in handles {
- handle.join().unwrap();
- }
- }
复制代码 在这段代码中,Arc<T>使得我们可以跨多个线程共享数据的引用,并确保线程安全。
4. 小结:常见的混淆点
- Vec<T>是Send但不是Sync:Vec可以被移动到其他线程,但它不能在多个线程中共享引用。
- Mutex<T>是Sync但不是Send:Mutex<T>提供互斥访问,但它不能在多个线程间通报,尤其是当其内部类型不是Send时。
- Arc<T>是Send和Sync:Arc<T>是一个线程安全的引用计数指针,可以在多个线程间共享,既能实现Send也能实现Sync。
明白这些眇小的差异对在Rust中编写安全的并发代码至关重要。Rust的全部权和借用系统确保了线程安全,但也要求开发者明确明白每种类型在多线程环境中的行为。通过合理利用Send和Sync,可以制止数据竞争和未界说行为,并写出更加结实的并发代码。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |