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

相关文章

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

Java高效实现PowerPoint转PDF的示例详解

《Java高效实现PowerPoint转PDF的示例详解》在日常开发或办公场景中,经常需要将PowerPoint演示文稿(PPT/PPTX)转换为PDF,本文将介绍从基础转换到高级设置的多种用法,大家... 目录为什么要将 PowerPoint 转换为 PDF安装 Spire.Presentation fo

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Java 线程池+分布式实现代码

《Java线程池+分布式实现代码》在Java开发中,池通过预先创建并管理一定数量的资源,避免频繁创建和销毁资源带来的性能开销,从而提高系统效率,:本文主要介绍Java线程池+分布式实现代码,需要... 目录1. 线程池1.1 自定义线程池实现1.1.1 线程池核心1.1.2 代码示例1.2 总结流程2. J

Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题

《Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题》在爬虫工程里,“HTTPS”是绕不开的话题,HTTPS为传输加密提供保护,同时也给爬虫带来证书校验、... 目录一、核心问题与优先级检查(先问三件事)二、基础示例:requests 与证书处理三、高并发选型:

Python中isinstance()函数原理解释及详细用法示例

《Python中isinstance()函数原理解释及详细用法示例》isinstance()是Python内置的一个非常有用的函数,用于检查一个对象是否属于指定的类型或类型元组中的某一个类型,它是Py... 目录python中isinstance()函数原理解释及详细用法指南一、isinstance()函数

python中的高阶函数示例详解

《python中的高阶函数示例详解》在Python中,高阶函数是指接受函数作为参数或返回函数作为结果的函数,下面:本文主要介绍python中高阶函数的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录1.定义2.map函数3.filter函数4.reduce函数5.sorted函数6.自定义高阶函数