rust从入门到放弃(十二):多线程

rust从入门到放弃(十二):多线程

首页动作格斗多线程游戏更新时间:2024-04-27

和其他语言一样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