ToB企服应用市场:ToB评测及商务社交产业平台

标题: Rust中的`Sync`与`Send`属性:明白并应用并发安全 [打印本页]

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

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


示例

  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


3. 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. 小结:常见的混淆点


明白这些眇小的差异对在Rust中编写安全的并发代码至关重要。Rust的全部权和借用系统确保了线程安全,但也要求开发者明确明白每种类型在多线程环境中的行为。通过合理利用Send和Sync,可以制止数据竞争和未界说行为,并写出更加结实的并发代码。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4