Rust:Future、async 异步代码机制示例与分析

2024-06-24 07:28

本文主要是介绍Rust:Future、async 异步代码机制示例与分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0. 异步、并发、并行、进程、协程概念梳理

Rust 的异步机制不是多线程或多进程,而是基于协程(或称为轻量级线程、微线程)的模型,这些协程可以在单个线程内并发执行。这种模型允许在单个线程中通过非阻塞的方式处理多个任务,从而实现高效的并发。

关于“并发”和“并行”的区别,这是两个经常被提及但含义不同的概念:

  • 并发(Concurrency):指的是同时处理多个任务的能力,这些任务可能在同一时间点开始、执行或结束,但不一定同时在物理硬件上执行。在单线程环境中,通过切换任务(例如,通过协程或事件循环)可以实现并发。

  • 并行(Parallelism):指的是同时执行多个任务,通常是在不同的处理单元(如CPU核心)上同时进行。这确实需要多线程或多进程环境,或者利用GPU、TPU等其他并行处理硬件。

Rust的异步机制非常适合处理高并发的I/O密集型任务,因为它可以有效地利用单个线程来处理多个任务,避免线程切换的开销,并通过非阻塞I/O减少等待时间。然而,它并不直接提供并行计算能力,即在多个物理处理单元上同时执行任务的能力。

对于需要并行计算能力的场景(例如,CPU密集型任务,如科学计算、大数据分析或图形渲染),Rust程序员通常会使用其他方法,如:

  • 使用多线程:通过Rust的标准库中的thread模块或其他并发原语(如crossbeamrayon等第三方库)来创建和管理多个线程。

  • 利用并行计算库:例如,使用OpenMP绑定的rust-omp库,或者使用专门为并行计算设计的语言和框架。

  • GPU加速:对于某些特定类型的计算密集型任务,可以利用GPU的并行处理能力,这通常需要使用专门的库(如cuda-sys用于CUDA编程)。

因此,虽然Rust的异步机制非常适合处理高并发的I/O密集型任务,但它本身并不直接支持并行计算。对于需要并行处理能力的场景,Rust程序员需要结合其他技术或库来实现。

1. Future

在Rust中,使用Future trait通常与.await语法结合,使得异步代码的编写和理解更加直观。以下是一个简单的例子,展示了如何定义一个实现Future trait的结构体,并使用.await来等待它的完成。

首先,请注意,直接实现Future trait是比较低级的操作,通常你会使用像async函数这样的高级抽象来间接创建Future。不过,为了教学目的,下面是一个简单的Future实现示例:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};// 定义一个简单的Future结构体
struct SimpleFuture {value: Option<i32>,
}// 为SimpleFuture实现Future trait
impl Future for SimpleFuture {type Output = i32;fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {// 在这个例子中,我们简单地假设Future已经完成,并返回一个固定的值if let Some(value) = self.value.take() {Poll::Ready(value) // 表示Future已经完成,并返回结果} else {Poll::Pending // 表示Future还未完成}}
}// 创建一个异步函数,它使用我们定义的SimpleFuture
async fn async_main() {// 创建一个新的SimpleFuture实例,这里我们初始化value为Some(42),意味着这个Future已经完成let future = SimpleFuture { value: Some(42) };// 使用.await语法等待Future完成,并获取结果let result = future.await;println!("Future result: {}", result);
}fn main() {// 在实际的Rust程序中,你需要一个运行时来执行异步代码,如tokio或async-std。// 这里为了简化,我们仅展示了Future的定义和使用,没有涉及具体的运行时。// 下面的代码块是伪代码,表示如何在一个异步运行时中执行async_main函数。// 假设的异步运行时执行函数// run_async(async_main()).unwrap();
}// 注意:要运行上面的异步代码,你需要集成一个异步运行时(如tokio或async-std),
// 并且使用适当的函数来启动异步任务。上面的`main`函数仅作为示意,并非实际可运行的代码。

2. async

在上面的代码中,SimpleFuture结构体实现了一个最简单的Future。然而,在实际应用中,你几乎总是会使用async函数和.await语法来编写异步代码,而不是直接实现Future trait。async函数会自动生成一个实现Future trait的类型,你无需手动实现它。

例如,下面是一个使用async函数的更实用的例子:

use std::time::Duration;#[tokio::main] // 使用tokio运行时
async fn main() {let result = do_something_async().await;println!("Async operation result: {}", result);
}async fn do_something_async() -> i32 {// 模拟一个耗时的异步操作,比如网络请求或数据库查询tokio::time::sleep(Duration::from_secs(1)).await; // 等待1秒42 // 返回某个结果
}

在这个例子中,do_something_async是一个async函数,它返回一个Future,该Future在1秒后解析为值42。我们使用tokio运行时来执行这个异步代码。注意,为了运行此代码,你需要在你的Cargo.toml中添加tokio作为依赖项。

3. 如何在异步代码执行期间做其他工作

在Rust的异步编程中,如果你想在do_something_async执行期间做其他事情,你可以使用tokio::spawn来并发地运行多个异步任务。这样,你可以在等待一个任务完成的同时执行其他任务。下面是一个示例代码,展示了如何在do_something_async执行期间执行另一个异步任务:

use std::time::Duration;
use tokio::task;#[tokio::main]
async fn main() {// 使用tokio::spawn并发运行do_something_asynclet handle = task::spawn(do_something_async());// 在此期间,你可以执行其他任务println!("Doing something else while waiting for the async operation...");tokio::time::sleep(Duration::from_secs(2)).await; // 模拟其他耗时操作,比如等待2秒println!("Other task completed.");// 现在等待之前的异步操作完成并获取结果let result = handle.await.unwrap(); // handle.await 返回 Result<T, JoinError>,我们需要unwrap来获取结果println!("Async operation result: {}", result);
}async fn do_something_async() -> i32 {// 模拟一个耗时的异步操作tokio::time::sleep(Duration::from_secs(3)).await; // 假设这个操作需要3秒42 // 返回结果
}

在这个例子中,do_something_async函数被tokio::spawn调用,从而在一个单独的异步任务中执行。主任务在do_something_async执行的同时,进行了一些其他操作(在这个例子中,是等待了2秒钟)。之后,主任务通过handle.await等待do_something_async的完成,并获取其结果。

请注意,tokio::spawn返回的是一个JoinHandle,它代表了一个独立的异步任务。通过在这个JoinHandle上调用.await,你可以等待该任务完成,并获取其返回值(如果存在的话)。在这个例子中,do_something_async返回一个i32类型的值,所以我们通过handle.await.unwrap()来获取这个值。如果异步任务在执行过程中发生了panic,那么handle.await会返回一个Err(JoinError::Panic(...))

4. tokio::spawn

那么上面例子中,tokio::spawn 是通过启动新线程实现的异步机制吗?

在Tokio中,tokio::spawn并不会直接启动新的线程。相反,它会在Tokio的运行时中异步地调度和执行任务。Tokio的运行时是一个基于异步I/O和多任务调度的系统,它通常使用少量的线程(通常与系统的CPU核心数相匹配)来高效地处理大量的并发任务。

当你调用tokio::spawn时,你提交的任务会被放入Tokio的任务队列中,等待被运行时中的某个工作线程执行。这些工作线程是Tokio运行时在初始化时创建的,并且会被复用来执行多个任务。因此,tokio::spawn本身不会为每个任务创建新的线程,而是利用现有的线程池来并发执行多个任务。

这种基于异步I/O和事件驱动的并发模型,使得Tokio能够在少量线程上高效地处理大量并发连接和任务,从而实现高吞吐量和低延迟的I/O操作。

5. 同一线程内的多个异步任务

给个具体例子:

use tokio::join; // 用于同时等待多个异步任务完成#[tokio::main] // 使用Tokio运行时
async fn main() {// 定义两个异步任务let task1 = async {// 模拟异步操作,比如网络请求或文件读取tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;println!("Task 1 completed");1 // 返回任务结果};let task2 = async {// 另一个模拟的异步操作tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;println!("Task 2 completed");2 // 返回任务结果};// 使用join!宏来等待两个任务同时完成let (result1, result2) = join!(task1, task2);// 输出结果println!("Result of task 1: {}", result1);println!("Result of task 2: {}", result2);
}

在上面的例子中,我们定义了两个异步任务task1task2,每个任务都会等待一段时间(模拟异步I/O操作)然后打印一条消息并返回一个结果。通过使用join!宏,我们可以同时等待这两个任务完成,并且获取它们的结果。

当你运行这个程序时,你会看到task2task1更早完成,因为它等待的时间更短。这展示了如何在单个线程中通过Tokio运行时并发执行多个异步操作。

请注意,虽然这些异步任务在逻辑上是并发的,但它们实际上是在单个线程上通过协程切换来执行的。Tokio运行时负责调度这些任务,使它们能够高效地共享线程资源。

这篇关于Rust:Future、async 异步代码机制示例与分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1089516

相关文章

github打不开的问题分析及解决

《github打不开的问题分析及解决》:本文主要介绍github打不开的问题分析及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、找到github.com域名解析的ip地址二、找到github.global.ssl.fastly.net网址解析的ip地址三

Java Multimap实现类与操作的具体示例

《JavaMultimap实现类与操作的具体示例》Multimap出现在Google的Guava库中,它为Java提供了更加灵活的集合操作,:本文主要介绍JavaMultimap实现类与操作的... 目录一、Multimap 概述Multimap 主要特点:二、Multimap 实现类1. ListMult

Linux使用scp进行远程目录文件复制的详细步骤和示例

《Linux使用scp进行远程目录文件复制的详细步骤和示例》在Linux系统中,scp(安全复制协议)是一个使用SSH(安全外壳协议)进行文件和目录安全传输的命令,它允许在远程主机之间复制文件和目录,... 目录1. 什么是scp?2. 语法3. 示例示例 1: 复制本地目录到远程主机示例 2: 复制远程主

Mysql的主从同步/复制的原理分析

《Mysql的主从同步/复制的原理分析》:本文主要介绍Mysql的主从同步/复制的原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录为什么要主从同步?mysql主从同步架构有哪些?Mysql主从复制的原理/整体流程级联复制架构为什么好?Mysql主从复制注意

深入解析 Java Future 类及代码示例

《深入解析JavaFuture类及代码示例》JavaFuture是java.util.concurrent包中用于表示异步计算结果的核心接口,下面给大家介绍JavaFuture类及实例代码,感兴... 目录一、Future 类概述二、核心工作机制代码示例执行流程2. 状态机模型3. 核心方法解析行为总结:三

python获取cmd环境变量值的实现代码

《python获取cmd环境变量值的实现代码》:本文主要介绍在Python中获取命令行(cmd)环境变量的值,可以使用标准库中的os模块,需要的朋友可以参考下... 前言全局说明在执行py过程中,总要使用到系统环境变量一、说明1.1 环境:Windows 11 家庭版 24H2 26100.4061

pandas实现数据concat拼接的示例代码

《pandas实现数据concat拼接的示例代码》pandas.concat用于合并DataFrame或Series,本文主要介绍了pandas实现数据concat拼接的示例代码,具有一定的参考价值,... 目录语法示例:使用pandas.concat合并数据默认的concat:参数axis=0,join=

java -jar命令运行 jar包时运行外部依赖jar包的场景分析

《java-jar命令运行jar包时运行外部依赖jar包的场景分析》:本文主要介绍java-jar命令运行jar包时运行外部依赖jar包的场景分析,本文给大家介绍的非常详细,对大家的学习或工作... 目录Java -jar命令运行 jar包时如何运行外部依赖jar包场景:解决:方法一、启动参数添加: -Xb

C/C++ chrono简单使用场景示例详解

《C/C++chrono简单使用场景示例详解》:本文主要介绍C/C++chrono简单使用场景示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友... 目录chrono使用场景举例1 输出格式化字符串chrono使用场景China编程举例1 输出格式化字符串示

C#代码实现解析WTGPS和BD数据

《C#代码实现解析WTGPS和BD数据》在现代的导航与定位应用中,准确解析GPS和北斗(BD)等卫星定位数据至关重要,本文将使用C#语言实现解析WTGPS和BD数据,需要的可以了解下... 目录一、代码结构概览1. 核心解析方法2. 位置信息解析3. 经纬度转换方法4. 日期和时间戳解析5. 辅助方法二、L