关于安全的多线程编程的问题,程序员们早已面临,常常遇到竞争条件、不一致的数据和内存泄漏。在 Rust 中,采用了一种独特的方法,使用 marker-traits Send 和 Sync,以便在编译阶段尽量减少这些问题。
问题在于缺乏对共享数据在线程之间访问的控制,这导致难以调试的错误。在许多语言中,这完全依赖于程序员,而在 Rust 中,编译器会检查哪些数据可以在线程之间传递或共享。
解决方案:trait Send 确保对象可以安全地从一个线程传递到另一个线程。Sync 则意味着可以安全地通过引用在不同线程中访问对象。几乎所有在 Rust 中的标准类型都会自动实现这些特性,而自定义类型可以手动实现它们或通过 impl !Send 或 impl !Sync 来禁止特定情况。
代码示例:
use std::sync::{Arc, Mutex}; use std::thread; let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } // counter 始终等于 10,且没有竞争!
关键特性:
包含不安全指针的类型可以是 Send 或 Sync 吗?
不可以,如果类型包含原始指针或没有线程安全保证的资源,则不会实现这些特性,或者开发者必须完全负责手动实现它们(通常是使用 unsafe impl Send/Sync)。
Rc<T> 和 RefCell<T> 是 Send 或 Sync 吗?
不,Rc<T> 和 RefCell<T> 对于多线程使用是不安全的(既不是 Send,也不是 Sync)。在多线程场景中使用 Arc<T> 和 Mutex/RwLock。
如果静态变量包含未实现 Sync 的类型会发生什么?
Rust 不允许这样的静态变量存在:它必须是 Sync,否则编译器会报错。
年轻的开发者将一个 Rc 对象放入 thread::spawn 中——如果 Rc 不在线程之间传递,代码将编译通过。尝试从 thread::spawn 中导出 Rc 会出现编译错误,因为 Rc 没有实现 Send,且没有保护以防竞争。
优点:
缺点:
使用 Arc+Mutex 实现多线程计数器,所有线程通过线程安全的接口使用相同的数据。没有竞争,代码安全且稳定。
优点:
缺点: