问题的历史:
与多线程工作相关是一大多数编程语言中的错误来源:数据竞争、资源争夺、隐蔽的漏洞。吸取C++和Java的经验,Rust的创建者决定将线程安全机制直接嵌入类型系统中,以便在编译期间发现大多数错误。
问题:
在传统语言中,往往依赖程序员的纪律和外部工具:数据所有权的传递风险、可变共享内存和缺乏对并发访问的控制可能导致严重崩溃。必须确保一个系统,在编译阶段就能保证没有内存竞争。
解决方案:
在Rust中,用于线程间同步和数据传递的特殊类型来自标准库——例如,Arc、Mutex和通道。关键角色是特征标记Send和Sync,这些会被编译器自动检查。类型被认为是线程安全的,如果:
Sync)Send的类型才能在线程之间传递代码示例:
use std::sync::{Arc, Mutex}; use std::thread; fn main() { 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(); } println!("结果: {}", *counter.lock().unwrap()); }
关键特性:
Mutex、RwLock、通道——根据合约是线程安全的Arc、Mutex)实现,而不是通过指针为什么不能使用Rc<T>在线程之间传递数据?
Rc<T>没有实现特征Send,并且不是线程安全的——其内部实现基于无锁引用计数,这导致多个线程访问时产生数据竞争。对于线程,请使用Arc<T>。
是否可以手动为自己的类型实现Send或Sync,以绕过编译器的限制?
可以,但这是非常危险的!如果违反不变性(例如,分享裸指针),将导致数据竞争。仅将手动实现留给完全自信于类型线程安全的专家。
在Rust中,Mutex何时可能导致死锁,如何避免?
如果多个互斥锁的获取顺序不稳定或在同一线程上递归地锁定,将导致死锁(Mutex不是可重入的!)。
use std::sync::Mutex; let a = Mutex::new(0); let _g1 = a.lock().unwrap(); let _g2 = a.lock().unwrap(); // panic: 死锁!
开发者在Web服务器中使用Rc<RefCell<T>>来传递状态。在本地测试中运作良好,但在生产环境中出现了竞争条件:变量有时会“丢失”状态,有时服务器崩溃。
优点:
缺点:
使用Arc<Mutex<T>>传递状态,严格遵守Send/Sync,通过通道分配工作到线程中,在线程中不使用mutable共享数据。
优点:
缺点: