和其他语言一样rust 也支持多线程,不过目前感觉使用多线程的最爽的还是 go ,因为你只需要执行 go 就可以启动一个协程。回到正题,我们说一下rust 的多线程。
rust创建线程,通过 spawn 方法创建线程,然后里面放入闭包,如下:
thread::spawn(move || {
// 线程逻辑
});
我们可以编写一个最简单的demo
fn main() {
let child = thread::spawn(move || {
println!("child");
});
println!("parent");
child.join();
}
上面的程序会输出
parent
child
其中 child 是由子线程输出的。
多线程总是有个避不开的问题,并发冲突,多个线程同时修改某个变量或者执行某段代码(临界区)的的时候就会出现冲突,这里冲突本质上是由于多CPU多核配合多级缓存导致的。我们来看下面这个例子,我们尝试对变量进行乘 2 操作。
fn main() {
let mut health = 12;
thread::spawn( || {
health *= 2;
});
println!("{}", health);
}
如果看过之前介绍闭包的文章,就会发现,这里的闭包采用的可变借用的方法捕获,这就需要保证,health 的生命周期要大于闭包,但这个闭包又是在另外一个线程中执行,编译器在编译代码的时候无法保证,所以会报错,在之前闭包的文章中,我们通过move 关键字把health的所有权转移到闭包里面。
fn main() {
let mut health = 12;
thread::spawn( move|| {
health *= 2;
});
println!("{}", health);
}
这时候代码虽然编译成功了,但是 health 的值没有修改。这个其实非常容易理解,因为这里使用了 move 相当于变量赋值, health 是基础类型,这里赋值是拷贝数据,闭包里面的health 和外部的health 没有关系,所以并不会影响外部的health ,这里聪明的童鞋又会想了,如果我传入一个 vec 复杂类型呢? 那么这里还有问题,当所有权move 专业到闭包里面了,外部的heatlh 将失效,最后一行打印将导致编译错误。什么鬼,难道rust 不支持多线程之间共享数据?
这当然不可能,但rust 只是阻止了线程之间不安全的共享。我们仍然可以通过锁机制在线程间完成共享,如下面代码启动了两个线程,分别对数据进行 100 和 -100 操作。这里关键是第一行里面 Arc 和Mutex ,其中 Mutex 是锁,保证多线程互斥,Arc 是引用计数,因为我们不能直接把 mutex 直接 move 到两个线程里面,这样就冲突了,所以我们通过clone 将引用计数 1 (不是拷贝),然后分别传到两个线程里面。这样两个线程就可以安全的使用同一个锁了。
const COUNT: u32 = 100;
fn main() {
let global = Arc::new(Mutex::new(0));
let clone1 = global.clone(); //引用计数 1
let thread1 = thread::spawn(move|| {
for _ in 0..COUNT {
let mut value = clone1.lock().unwrap();//获取锁
*value = 1;
} });
let clone2 = global.clone(); //引用计数 1
let thread2 = thread::spawn(move|| {
for _ in 0..COUNT {
let mut value = clone2.lock().unwrap();//获取锁
*value -= 1;
} });
thread1.join().ok();
thread2.join().ok();
println!("final value: {:?}", global);
}
程序安全并发,最终输出为 0 。这里并没有单独释放锁,因为每次for 循环执行完,value 生命周期结束,锁自然就释放了。整个模型如下所示:
最终的数据被 Mutex 保护起来,然后多个线程通过 Arc 引用共享这个锁。除了上面的互斥锁,rust 也支持 RwLock 读写锁 和 Atomic 原子操作。其中,读写锁使用方式如下
let global = Arc::new(RwLock::new(0));
let mut value = clone1.write().unwrap();
let mut value = clone1.read().unwrap();
原子操作使用方式如下:
let global = Arc::new(AtomicIsize::new(0));
clone1.fetch_add(1, Ordering::SeqCst); // 加法
clone2.fetch_sub(1, Ordering::SeqCst); // 减法
那么是不是任何类型都可以通过上面的move 传递到线程里面呢,当然也不是,这里就涉及到rust 里面Send 和Sync 这两个trait ,关于Send 和 Sync 相关内容等后面文章单独介绍。
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved