【Rust】——使用线程同时运行代码
Y小夜 2024-06-22 09:05:02 阅读 57
💻博主现有专栏:
C51单片机(STC89C516),c语言,c++,离散数学,算法设计与分析,数据结构,Python,Java基础,MySQL,linux,基于HTML5的网页设计及应用,Rust(官方文档重点总结),jQuery,前端vue.js,Javaweb开发,Python机器学习等
🥏主页链接:
Y小夜-CSDN博客
目录
🎯使用soawn创建新线程
🎯使用join等待所有线程结束
🎯将move闭包与线程一同使用
在大部分现代操作系统中,已执行程序的代码在一个 进程(process)中运行,操作系统则会负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。这些运行这些独立部分的功能被称为 线程(threads)。例如,web 服务器可以有多个线程以便可以同时响应多个请求。
将程序中的计算拆分进多个线程可以改善性能,因为程序可以同时进行多个任务,不过这也会增加复杂性。因为线程是同时运行的,所以无法预先保证不同线程中的代码的执行顺序。这会导致诸如此类的问题:
竞态条件(Race conditions),多个线程以不一致的顺序访问数据或资源死锁(Deadlocks),两个线程相互等待对方,这会阻止两者继续运行只会发生在特定情况且难以稳定重现和修复的 bug
Rust 尝试减轻使用线程的负面影响。不过在多线程上下文中编程仍需格外小心,同时其所要求的代码结构也不同于运行于单线程的程序。
编程语言有一些不同的方法来实现线程,而且很多操作系统提供了创建新线程的 API。Rust 标准库使用 1:1 线程实现,这代表程序的每一个语言级线程使用一个系统线程。有一些 crate 实现了其他有着不同于 1:1 模型取舍的线程模型。
🎯使用soawn创建新线程
为了创建一个新线程,需要调用
thread::spawn
函数并传递一个闭包,并在其中包含希望在新线程运行的代码。示例 16-1 中的例子在主线程打印了一些文本而另一些文本则由新线程打印:
use std::thread;use std::time::Duration;fn main() { thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); }}
注意当 Rust 程序的主线程结束时,新线程也会结束,而不管其是否执行完毕。这个程序的输出可能每次都略有不同,不过它大体上看起来像这样:
hi number 1 from the main thread!hi number 1 from the spawned thread!hi number 2 from the main thread!hi number 2 from the spawned thread!hi number 3 from the main thread!hi number 3 from the spawned thread!hi number 4 from the main thread!hi number 4 from the spawned thread!hi number 5 from the spawned thread!
thread::sleep
调用强制线程停止执行一小段时间,这会允许其他不同的线程运行。这些线程可能会轮流运行,不过并不保证如此:这依赖操作系统如何调度线程。在这里,主线程首先打印,即便新创建线程的打印语句位于程序的开头,甚至即便我们告诉新建的线程打印直到i
等于 9,它在主线程结束之前也只打印到了 5。
🎯使用join等待所有线程结束
可以通过将
thread::spawn
的返回值储存在变量中来修复新建线程部分没有执行或者完全没有执行的问题。thread::spawn
的返回值类型是JoinHandle
。JoinHandle
是一个拥有所有权的值,当对其调用join
方法时,它会等待其线程结束。
use std::thread;use std::time::Duration;fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap();}
通过调用 handle 的
join
会阻塞当前线程直到 handle 所代表的线程结束。阻塞(Blocking)线程意味着阻止该线程执行工作或退出。因为我们将join
调用放在了主线程的for
循环之后,运行示例 16-2 应该会产生类似这样的输出:
hi number 1 from the main thread!hi number 2 from the main thread!hi number 1 from the spawned thread!hi number 3 from the main thread!hi number 2 from the spawned thread!hi number 4 from the main thread!hi number 3 from the spawned thread!hi number 4 from the spawned thread!hi number 5 from the spawned thread!hi number 6 from the spawned thread!hi number 7 from the spawned thread!hi number 8 from the spawned thread!hi number 9 from the spawned thread!
这两个线程仍然会交替执行,不过主线程会由于
handle.join()
调用会等待直到新建线程执行完毕。
use std::thread;use std::time::Duration;fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); handle.join().unwrap(); for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); }}
hi number 1 from the spawned thread!hi number 2 from the spawned thread!hi number 3 from the spawned thread!hi number 4 from the spawned thread!hi number 5 from the spawned thread!hi number 6 from the spawned thread!hi number 7 from the spawned thread!hi number 8 from the spawned thread!hi number 9 from the spawned thread!hi number 1 from the main thread!hi number 2 from the main thread!hi number 3 from the main thread!hi number 4 from the main thread!
🎯将move闭包与线程一同使用
move
关键字经常用于传递给thread::spawn
的闭包,因为闭包会获取从环境中取得的值的所有权,因此会将这些值的所有权从一个线程传送到另一个线程。
传递给
thread::spawn
的闭包并没有任何参数:并没有在新建线程代码中使用任何主线程的数据。为了在新建线程中使用来自于主线程的数据,需要新建线程的闭包获取它需要的值。
use std::thread;fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { println!("Here's a vector: {:?}", v); }); handle.join().unwrap();}
闭包使用了
v
,所以闭包会捕获v
并使其成为闭包环境的一部分。因为thread::spawn
在一个新线程中运行这个闭包,所以可以在新线程中访问v
。然而当编译这个例子时,会得到如下错误:
$ cargo run Compiling threads v0.1.0 (file:///projects/threads)error[E0373]: closure may outlive the current function, but it borrows `v`, which is owned by the current function --> src/main.rs:6:32 |6 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `v`7 | println!("Here's a vector: {:?}", v); | - `v` is borrowed here |note: function requires argument type to outlive `'static` --> src/main.rs:6:18 |6 | let handle = thread::spawn(|| { | __________________^7 | | println!("Here's a vector: {:?}", v);8 | | }); | |______^help: to force the closure to take ownership of `v` (and any other referenced variables), use the `move` keyword |6 | let handle = thread::spawn(move || { | ++++For more information about this error, try `rustc --explain E0373`.error: could not compile `threads` due to previous error
Rust 会 推断 如何捕获
v
,因为println!
只需要v
的引用,闭包尝试借用v
。然而这有一个问题:Rust 不知道这个新建线程会执行多久,所以无法知晓对v
的引用是否一直有效。
use std::thread;fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { println!("Here's a vector: {:?}", v); }); drop(v); // oh no! handle.join().unwrap();}
如果 Rust 允许这段代码运行,则新建线程则可能会立刻被转移到后台并完全没有机会运行。新建线程内部有一个
v
的引用,不过主线程立刻就使用第十五章讨论的drop
丢弃了v
。接着当新建线程开始执行,v
已不再有效,所以其引用也是无效的。噢,这太糟了!
通过在闭包之前增加
move
关键字,我们强制闭包获取其使用的值的所有权,而不是任由 Rust 推断它应该借用值。示例 16-5 中展示的对示例 16-3 代码的修改,可以按照我们的预期编译并运行:
use std::thread;fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(move || { println!("Here's a vector: {:?}", v); }); handle.join().unwrap();}
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。