一、使用 Send
实现所有权的转移
Send
标记特征表示一个类型的所有权可以安全地在不同的线程之间传递。实现 Send
的类型可以在不引发未定义行为(例如数据竞争)的情况下,将其所有权从一个线程传递到另一个线程。
1.1.为什么 Rc<T>
不是 Send
尽管大多数类型在 Rust 中都实现了 Send
,但是有些类型并没有实现。例如,Rc<T>
就没有实现 Send
,原因在于它使用了引用计数,这不是线程安全的。如果两个线程同时更新引用计数,就可能发生数据竞争,导致未定义行为。
例如,以下代码尝试将一个 Rc<T>
跨线程传递:
use std::rc::Rc;
use std::thread;
fn main() {
let value = Rc::new(5);
let handle = thread::spawn(move || {
println!("{}", value); // 错误:`Rc<T>` 不是 `Send`
});
handle.join().unwrap();
}
这段代码会报错,因为 Rc<T>
不能被发送到线程中,缺少 Send
特征。
为了解决这个问题,Rust 提供了 Arc<T>
(原子引用计数),它是线程安全的并实现了 Send
。下面是修改后的代码,使用了 Arc<T>
:
use std::sync::Arc;
use std::thread;
fn main() {
let value = Arc::new(5);
let handle = thread::spawn(move || {
println!("{}", value); // 安全:`Arc<T>` 实现了 `Send`
});
handle.join().unwrap();
}
1.2.如何使类型变为 Send
任何完全由 Send
类型组成的类型也会自动被认为是 Send
。因此,只要一个结构体的字段都实现了 Send
,那么整个结构体也会实现 Send
。这确保了只要组件是安全的跨线程传递,整个类型也能安全地传递。
例如:
use std::sync::Arc;
use std::sync::Mutex;
struct MyStruct {
data: Arc<Mutex<i32>>,
}
fn main() {
let my_data = MyStruct {
data: Arc::new(Mutex::new(42)),
};
// `MyStruct` 是 `Send`,因为 `Arc` 和 `Mutex<i32>` 都是 `Send`
}
二、使用 Sync
实现安全的访问
Sync
标记特征表示一个类型可以在多个线程之间安全地访问,只要没有对它的可变引用。一个类型 T
如果 &T
(对 T
的不可变引用)可以在多个线程之间安全共享,那么 T
就是 Sync
。
2.1.为什么 Rc<T>
不是 Sync
与 Send 类似,
Rc也不是
Sync,因为它的引用计数机制不是线程安全的。如果你试图在多个线程中同时访问一个
Rc`,就会发生数据竞争。
相比之下,Mutex<T>
是 Sync
的,因为它确保每次只有一个线程可以访问底层数据,从而防止了数据竞争。
以下是一个例子:
use std::rc::Rc;
use std::thread;
fn main() {
let value = Rc::new(5);
// 错误:`Rc<T>` 不是 `Sync`,所以不能在多个线程之间共享
let handle = thread::spawn(move || {
println!("{}", value); // 错误
});
handle.join().unwrap();
}
与此不同的是,Mutex<T>
可以在多个线程之间安全共享,因为它在访问底层数据时会加锁:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let value = Arc::new(Mutex::new(5));
let handle = thread::spawn(move || {
let mut num = value.lock().unwrap(); // 锁定 Mutex
*num += 1; // 安全地修改数据
println!("{}", *num);
});
handle.join().unwrap();
}
在这种情况下,Mutex<T>
确保了安全访问,代码能够编译并按预期运行。
三、手动实现 Send
和 Sync
虽然 Rust 会自动为许多类型实现 Send
和 Sync
,但有时你可能需要手动实现这些特征。然而,手动实现这些特征是 unsafe
的,需要对 Rust 的并发模型有深入的了解。如果你手动实现这些特征,你必须确保该类型遵守必要的安全保证。
关于这一点,你可以参考《Rustonomicon》,它深入探讨了不安全的 Rust 和实现这些特征时需要遵守的规则。
四、总结
在 Rust 中,并发是通过 Send
和 Sync
特征集成到类型系统中的。这些特征允许语言在编译时强制执行并发安全,防止了数据竞争和无效引用等常见问题。通过使用如 Arc<T>
、Mutex<T>
和 RwLock<T>
等类型,Rust 提供了线程安全的抽象,确保你的代码在并发场景下是安全且高效的。
Rust 的并发方式不仅防止了错误,还确保了你不需要担心在其他语言中的多线程程序常见的 bug。一旦你的代码编译通过,你可以放心它会在多线程环境中安全地运行。
如果你准备深入了解 Rust 的并发模型,建议你探索标准库中的并发工具,并在接下来的章节中学习异步编程。祝编程愉快!