Rust 并发执行异步操作
在 Rust 中,并发执行异步操作可以显著提升程序性能。本文将深入探讨两种常见的并发策略:多线程 和 Futures 联合。
多线程概述
线程本质上是 CPU 执行的一段软件代码序列。我们可以将其理解为一个运行进程的容器。多线程允许我们同时运行多个任务,从而提高程序性能,但同时也引入了复杂性。
创建和管理线程
使用 std::thread::spawn
函数可以创建新的线程:
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..100 {
println!("{} 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));
}
}
这段代码创建了一个新的线程,并在其中执行一个循环,打印数字并休眠。主线程也执行一个类似的循环。
Join 线程
主线程结束时,所有子线程都会被强制关闭,无论它们是否已完成任务。为了确保子线程完成,我们可以使用 JoinHandle
将子线程的返回值绑定到一个变量,并使用 join
方法等待子线程完成。
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("{} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("{} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().expect("error joining");
}
Join 线程的位置
join
方法的位置至关重要。如果将 join
方法放在主线程循环之前,主线程将等待子线程完成后再执行。
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("{} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
handle.join().expect("error joining");
for i in 1..5 {
println!("{} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
从 JoinHandle 获取返回值
通过 JoinHandle
获取子线程的返回值,例如:
fn main() {
let handle_1 = thread::spawn(|| {
for i in 1..10 {
println!("{} from the spawned thread 1!", i);
thread::sleep(Duration::from_millis(1));
}
100
});
let handle_2 = thread::spawn(|| {
for i in 1..10 {
println!("{} from the spawned thread 2!", i);
thread::sleep(Duration::from_millis(1));
}
200
});
let result_1 = handle_1.join().expect("error joining");
let result_2 = handle_2.join().expect("error joining");
println!("final result: {} from the main thread!", result_1 + result_2);
}
使用 move 关键字
如果闭包需要获取外部变量的所有权,可以使用 move
关键字:
fn main() {
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("vector: {:?}", v);
});
handle.join().expect("error joining");
}
异步操作与多线程
使用 tokio::spawn 创建异步线程
tokio::spawn
可以用来创建异步线程:
use std::{thread, time::Duration};
use tokio;
#[tokio::main]
async fn main() {
let spawn_1 = tokio::spawn(async {
for i in 1..5 {
println!("{} from the thread 1!", i);
thread::sleep(Duration::from_millis(1));
}
});
let spawn_2 = tokio::spawn(async {
for i in 1..5 {
println!("{} from the thread 2!", i);
thread::sleep(Duration::from_millis(1));
}
});
spawn_1.await.expect("error awaiting");
spawn_2.await.expect("error awaiting");
}
tokio::spawn
创建的异步任务可能在当前线程或其他线程上执行,具体取决于运行时的配置。
Futures 联合
顺序执行 Futures
异步函数返回 Future
类型,可以使用 await
获取结果:
#[tokio::main]
async fn main() {
let start = Instant::now();
let future_1 = async_operation(1);
let future_2 = async_operation(2);
future_1.await;
future_2.await;
println!("{}: {:?}", "futures: ", start.elapsed());
}
async fn async_operation(thread: i8) {
for i in 1..5 {
println!("{} from the operation {}!", i, thread);
tokio::time::sleep(Duration::from_millis(400)).await;
thread::sleep(Duration::from_millis(100));
}
}
并发执行 Futures
为了并发执行异步操作,可以使用 futures::future::join_all
:
#[tokio::main]
async fn main() {
let start = Instant::now();
let future_1 = async_operation(1);
let future_2 = async_operation(2);
join_all([future_1, future_2]).await;
println!("{}: {:?}", "futures: ", start.elapsed());
}
总结
多线程 vs Futures 联合
- 多线程:适合长时间运行的独立、内存或 CPU 密集型任务。
- Futures 联合:适合短时间运行、依赖 IO 操作或无需返回值的任务。
选择合适的并发策略
在选择并发策略时,需要考虑任务的类型、依赖关系、资源限制等。
希望本文能够帮助您更好地理解 Rust 中的并发编程,并选择合适的策略来提高您的程序性能。