线程通讯方式
线程之间的通讯方式最常见的就是通过共享内存的方式,再通过加锁保护数据在多个线程中的一致性。
Rust语言同样有这种支持那就是Mutex<T>互斥器。
互斥器
通过互斥器可以申明一个数据是互斥的,要使用互斥器中的数据必须持有锁,当离开作用域之后会自动释放锁
mutex.png
多线程中使用互斥器
//java code
AtomicInteger counter = new AtomicInteger(0);
for(int i = 0 ; i< 3 ; ++i)
new Thread(()-> counter.addAndGet(1)).start();
Thread.sleep(1000);
assert 3==counter.get();
上面是Java中多个线程共同操作同一个线程安全的状态。通过for循环创建三个线程,线程间共享一个线程安全的计数器变量,完成计数操作。
下面通过Rust完成同样的操作。需要注意的是Rust中的所有权,当通move将外部counter的所有权转移到线程内之后外部作用域是无法操作counter的。因此这里需要使用引用计数智能指针,Rc<T>引用计数指针是非线程安全的,因此需要使用Arc<T>(原子引用计数)来通过牺牲部分性能的方式保证线程安全。
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..3{
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut hold_lock = counter.lock().unwrap();
*hold_lock+=1;
});
handles.push(handle);
}
for handel in handles {
handel.join().unwrap();
}
assert_eq!(*counter.lock().unwrap(),3);
死锁
Rust并不能完全避免死锁或“线程不安全”,下面是一段产生死锁的代码。
fn dead_lock(){//This is a dead lock code
let p = Arc::new(Mutex::new(0));
let q = Arc::clone(&p);
let th = thread::spawn(move || {
let mut i = p.lock().unwrap();
//*i+=1;
});
let mut j = q.lock().unwrap();
//*j+=1;
th.join().unwrap();
}
主线程率先拿到锁,之后等待th线程结束,th线程此时由于锁被主线程拿到了需要等待主线程释放锁才能获取锁,因此th线程会一直等待主线程释放,主线程会一直等待th线程执行完毕,最终两个线程会永远等待下去。
解决方案就是在th.join().unwrap();之前释放掉主线程拿到的锁,因此可以将let mut j = q.lock().unwrap();放入一个代码块
// let mut j = q.lock().unwrap();
{let mut j = q.lock().unwrap();}
通过前面所讲到的离开作用域即释放锁这一特性来保证锁被提前释放。
总结
使用Rust 中的 Mutex(互斥器)能够在线程之间共享状态,使用流程大致是
- 在使用数据之前尝试获取锁(object.lock().unwrap())。
- 处理完被互斥器所保护的数据之后,必须解锁数据,这样其他线程才能够获取锁(离开作用域)。
其中释放锁是通过实现Drop trait来完成的,Rust并不能逻辑错误导致的死锁,因此编写代码时要注意合适取锁合适释放,避免逻辑错误。
网友评论