Rust中的`Sync`与`Send`属性:明白并应用并发安全

打印 上一主题 下一主题

主题 968|帖子 968|积分 2904

在Rust的并发编程中,Sync和Send是两个重要的标准库特性(traits),它们界说了类型在多线程环境下的行为。Rust以其严格的全部权和借用系统确保内存安全,制止了传统并发编程中的很多常见问题。本文将详细解释Sync与Send的含义、利用场景以及常见的混淆点。
1. 什么是Send?

Send是一个标记trait,用于标识一个类型可以在线程之间通报。假如一个类型实现了Send,则可以通过全部权转移(而非引用共享)将其从一个线程通报到另一个线程。在Rust中,全部权转移时会自动处理内存管理,制止了传统并发模子中的数据竞争问题。
Send的关键点



  • Send表示类型能够在线程之间通报,通常是通过全部权转移。
  • 假如一个类型实现了Send,则可以安全地将其通报给另一个线程。
  • 原生类型(如整数、浮点数、布尔值等)和大多数实现了Send的类型可以在多个线程间通报。
示例

  1. use std::thread;
  2. fn main() {
  3.     let data = vec![1, 2, 3, 4, 5];
  4.     let handle = thread::spawn(move || {
  5.         println!("{:?}", data); // 数据被移动到新线程
  6.     });
  7.     handle.join().unwrap();
  8. }
复制代码
在上面的代码中,data是一个Vec<i32>,它实现了Send,因此可以安全地通报到新线程。这里的move关键字将data的全部权转移到新的线程中,而不会产生数据竞争。
非Send类型

某些类型不能实现Send,尤其是那些包含不可变引用或引用计数的类型。比方,Rc<T>(引用计数指针)和RefCell<T>(可变借用类型)都不能在多个线程之间通报,因为它们依赖于线程局部存储的引用计数或可变借用,这在多线程环境中是不安全的。
  1. use std::rc::Rc;
  2. use std::thread;
  3. fn main() {
  4.     let rc = Rc::new(5);
  5.     let handle = thread::spawn(move || {
  6.         println!("{:?}", rc); // 编译错误,Rc不能被跨线程传递
  7.     });
  8.     handle.join().unwrap();
  9. }
复制代码
这段代码会编译失败,因为Rc不能在多个线程间通报。
2. 什么是Sync?

Sync是另一个标记trait,用来标识一个类型可以安全地在多个线程之间共享引用。换句话说,假如一个类型实现了Sync,那么它的引用可以在多个线程中共享而不会引发数据竞争。
Sync的关键点



  • Sync表示类型的引用可以在多个线程间共享。
  • 只有当类型的内部数据能够被多个线程安全地访问时,它才会实现Sync。
  • 假如类型实现了Sync,则该类型的引用(如&T)可以在线程间共享。
示例

  1. use std::sync::{Arc, Mutex};
  2. use std::thread;
  3. fn main() {
  4.     let data = Arc::new(Mutex::new(0));
  5.     let mut handles = vec![];
  6.     for _ in 0..10 {
  7.         let data = Arc::clone(&data);
  8.         let handle = thread::spawn(move || {
  9.             let mut num = data.lock().unwrap();
  10.             *num += 1;
  11.         });
  12.         handles.push(handle);
  13.     }
  14.     for handle in handles {
  15.         handle.join().unwrap();
  16.     }
  17.     println!("Result: {}", *data.lock().unwrap());
  18. }
复制代码
在这段代码中,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>的引用不能在多个线程之间共享。
  1. use std::thread;
  2. fn main() {
  3.     let vec = vec![1, 2, 3, 4, 5];
  4.     let handle = thread::spawn(move || {
  5.         println!("{:?}", vec); // vec 被移动到新的线程
  6.     });
  7.     handle.join().unwrap();
  8. }
复制代码
在这段代码中,Vec<T>的全部权被安全地通报到新线程。但是,Vec<T>自己不能在多个线程中共享引用,因为它并没有提供任何同步机制来保证数据的同等性。
Mutex<T>是Sync但不是Send

Mutex<T>是一个提供互斥访问的数据结构,它允许一个线程锁定并修改数据,但它自己并不总是Send,特殊是当它内部存储的数据类型不是Send时。
  1. use std::sync::Mutex;
  2. use std::rc::Rc;
  3. use std::thread;
  4. fn main() {
  5.     let data = Rc::new(5);
  6.     let mutex = Mutex::new(data);
  7.     let handle = thread::spawn(move || {
  8.         let _locked_data = mutex.lock().unwrap(); // 编译错误,因为Rc不能被跨线程传递
  9.     });
  10.     handle.join().unwrap();
  11. }
复制代码
由于Rc<T>不是Send的,所以Mutex<Rc<T>>也不能在多个线程之间通报。
Arc<T>是Send和Sync

Arc<T>(原子引用计数)是一个线程安全的智能指针,它允许在多个线程之间共享全部权。Arc<T>实现了Send和Sync,因此它的引用可以在多个线程中安全地共享和通报。
  1. use std::sync::Arc;
  2. use std::thread;
  3. fn main() {
  4.     let data = Arc::new(5);
  5.     let handles: Vec<_> = (0..10).map(|_| {
  6.         let data = Arc::clone(&data);
  7.         thread::spawn(move || {
  8.             println!("{}", data); // 共享引用
  9.         })
  10.     }).collect();
  11.     for handle in handles {
  12.         handle.join().unwrap();
  13.     }
  14. }
复制代码
在这段代码中,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企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表