async/await 编程理解

博客主要是参考 Asynchronous Programming in Rust ,会结合简单的例子,对 async 和 await 做比较系统的理解,如何使用 async 和 await 是本节的重点。

async 和 await 主要用来写异步代码,async 声明的代码块实现了 Future 特性,如果实现 Future 的代码发生阻塞,会让出当前线程的控制权,允许线程去执行别的 Future 代码。

我把 async 代码块使用 Future 来表示,关键是理解 future 和执行线程之间的关系。系统中存在多个 future时,多个 future 的执行顺序如何控制?以及多个 future 如何同时并发执行?文章会尝试解释这些问题。

在这里插入图片描述

多个 async 串行执行

在 Cargo.toml 中增加如下依赖。

[dependencies]
futures = "0.3.28"

使用 async fn 创建异步方法,关键字 async 还可以用来声明代码块,在闭包函数中可能会见到。

async fn do_something() { /* ... */ }

我对原始内容的例子做了简化,来探究异步执行的过程。仍然声明了三个 async 方法,分别对应「学习唱歌」、「唱歌」、「跳舞」三个函数,假设这三个函数可以并行异步执行,那么这三个过程的顺序便是随机的、不确定的。

use futures::executor::block_on;

async fn learn_song() { 
    println!("learn song");
}

async fn sing_song() { 
    println!("sing song");
}

async fn dance() {
    println!("dance");
}

async fn async_main() {
    let f1 = learn_song();
    let f2 = sing_song();
    let f3 = dance();
    futures::join!(f1, f2, f3);
}

fn main() {
    block_on(async_main());
}

本地执行的结果是,上述代码无论执行多少次,输出都是确定的。理论上确实不应该,我都有点怀疑:会不会代码太简单导致的。

修改一下 learn_song 函数,在打印之前执行多次 for 循环。如果这几个过程是并发的,那么 learn_song 肯定是最后被执行完成的。遗憾的是,控制台第一个输出的还是 “learn song”。这也说明,上述代码并没有被并发执行。

async fn learn_song() { 
    let mut i = 0;
    while i < 10000 {
        i = i + 1;
    }
    println!("learn song");
}

原文代码示例

问题究竟出在哪里了呢?重新看一下原文章的demo,试着对比一下哪里出了问题。

async fn learn_and_sing() {
    // Wait until the song has been learned before singing it.
    // We use `.await` here rather than `block_on` to prevent blocking the
    // thread, which makes it possible to `dance` at the same time.
    let song = learn_song().await;
    sing_song(song).await;
}

async fn async_main() {
    let f1 = learn_and_sing();
    let f2 = dance();

    // `join!` is like `.await` but can wait for multiple futures concurrently.
    // If we're temporarily blocked in the `learn_and_sing` future, the `dance`
    // future will take over the current thread. If `dance` becomes blocked,
    // `learn_and_sing` can take back over. If both futures are blocked, then
    // `async_main` is blocked and will yield to the executor.
    futures::join!(f1, f2);
}

fn main() {
    block_on(async_main());
}

join!await 类似,只是join!能够等待多个并发执行的 future,如果代码被临时阻塞在 learn_and_singdance 会被当前线程接管。如果 dance 被阻塞,learn_and_sing 会重新被接管。如果两个方法同时被阻塞,async_main 就会被阻塞,会使当前执行阻塞。

通过代码注释,我抠到一个字眼: the current thread,这难道说明函数 f1 和 f2 是在一个线程上执行的?如果它们是在一个线程上执行的,又因为方法体内部都没有类似网络IO的阻塞,那确实有可能导致串行执行的效果。

rust 对 async 的支持

重新翻看语言和库的支持 Language and library support介绍,我决定把中心放在 async 的设计实现上,async 在底层是如何被处理的。

  1. 最基础的特性、类型和方法,比如 Future 特性都被标准库提供
  2. async/await 语法直接被 Rust 编译器支持
  3. futures 库提供了一些工具类型、宏、函数,它们可以被使用在 Rust 应用程序中
  4. async runtimes 比如 Takio、async-std,提供了执行一部代码、IO、创建任务的能力。大部分的异步应用和异步库都依赖具体的运行时,可以查看 The Async Ecosystem 了解细节

关键点在异步运行时上,原来 rust 只提供了一套异步运行时的接入标准,具体的实现则是由第三方库自己决定的,比较有名的的运行时三方库包括 Takio 和 async-std。不得不说,Rust 设计的眼界确实比较高。

我想通过 Single Threaded vs Multi-Threaded Executors 来理解单线程和多线程的执行。前面的例子中,我们有怀疑:代码没有并行是因为在单线程中执行导致的。当然,也可能是因为我们没有明确指定 async runtimes。

异步的 executor 可以是单线程、也可以是多线程执行,async-executor 库就同时提供了这两种 executor
.
多线程 executor 可以同时处理多个任务,这会大大加快了处理速度。但任务之间的数据同步成本也会更高一些。建议通过性能测试来决策选择单线程还是多线程
.
任务既可以被创建它的线程执行,也可以被其他独立线程执行。即使任务被独立线程执行,其运行也是非阻塞的。如果想要调度任务在多线程上执行,任务本身必须要实现 Send 特性。有的运行时还支持创建 non-Send 任务,用来保证每个任务都被创建它的线程执行。有的库中还支持将生成的阻塞任务交给特定的线程去执行,这对于运行阻塞代码会非常有用

看到这里,我决定指定 Takio 运行时来重新运行上述代码,如何指定呢?

指定 Takio 运行时

takio 的官网挺花里胡哨,大大的标题:Build reliable network applications without compromising speed. 直译过来是构建可靠的网络应用而不向降低运行效率妥协。不过理性提醒我:这种既要又要的声明,应该得有前提吧。

takio 中的例子是 redis 的写入和读取过程,我要介绍的内容都基于 Hello Tokio 示例的解释,只不过,解释的顺序上会有一点点调整。

如何开启异步运行时

async fn 必须被异步运行时执行,运行时包含异步任务调度、IO事件、计时器等。但这个运行时并不会自动开启,我们可以通过 main 函数来开启它。
.
#[tokio::main] 宏可以将 async fn main() 转换为同步的 fn main(),并为它初始化了运行时实例,然后执行异步的 async main 函数

在这里插入图片描述
在我的例子中,main 函数最后也调用了 block_on 函数,这里调用的是 rt.block_on 方法,不晓得两者是否存在差异?不过,后文有必要等会去看看,在没有指定运行时的情况下 block_on 的执行效果。

Cargo features

引入 tokio 依赖,其中的 features 属性可以指定引入的范围。现在,我们使用 full 来标识导入所有特性。不过,我感觉引入 tokio 会和例子中的 futures 发生冲突,引入 tokio 后,明显就不需要引入 futures::executor::block_on 了。

还是得继续看看测试效果,用上面的例子来验证一下

[dependencies]
tokio = { version = "1", features = ["full"] }

异步程序

对于同步程序来说,如果程序执行过程中遇到阻塞操作,它就会阻塞在那里直到程序处理完成。拿建立 TCP 连接来说,需要建连的两端通过网络进行数据交互,但这个过程会花费相当多的时间,线程就只能阻塞,直到建连完成。
.
对于异步程序来说,不能立即完成的操作会被系统挂起到后台,当前线程并不会被阻塞,它仍可以继续执行别的任务。一旦被挂起的任务恢复,它便可以从上次执行的位置重新开始执行。因为我们的例子中只有一个任务,因此当操作被挂起时,并没有触发别的任务执行,但异步程序通常是会包含多个任务的。

关于异步的解释,这里特意提到了任务:task。只有在多个 task 的情况下,异步执行才有意义。上文还一直提到的 executor、recator,它们都算得上异步运行时的关键模块。

我忍不住思考,最初例子中的「学习唱歌」、「唱歌」、「跳舞」是属于三个独立的任务吧,应该是的。

Compile-time green-threading

green-threading 阅读到这里的小伙伴,欢迎留言来解释,它的具体含义。点击链接可以直接跳转到对应的原文。

异步回归

首先引入 tokio 的包,其次,使用 #[tokio::main] 来标识开启异步运行时,删除之前的 main 函数,简单替换成下面的 main 函数声明。非常遗憾,下面的代码仍然无法异步执行。

#[tokio::main]
async fn main() {
    let f1 = learn_song();
    let f2 = sing_song();
    let f3 = dance();
    futures::join!(f1, f2, f3);
}

难不成是因为 task ,我们现在看看 tokio 中对 task 的用法,现在的代码只是有了 tokio 的面,并没有 tokio 的里。

task

tokio task 是一个异步的 green 线程,需要通过 tokio::spawn 方法进行创建,该方法返回 JoinHandle 类型对象,调用者需要通过这个返回值来和该 task 进行交互。如果 task 包含返回值,调用者可以链式对 JoinHandle 调用 await来获取。

结合下面的例子,看起来 .await 是用来阻塞等待 task 执行完成的。启动一个 task 之后,必须确保 task 执行完成,才能继续执行后续的依赖流程,有点类似 java 的异步线程。

#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async {
        // Do some async work
        "return value"
    });

    // Do some other work

    let out = handle.await.unwrap();
    println!("GOT {}", out);
}

在 JoinHandle 上调用 await 会返回一个 Result 对象。如果 task 在执行期间发生错误,JoinHandle 会返回一个 Err 类型。当 task 发生 panic,或者被运行时强制取消时便会触发这个 Err。

task 是运行时调度的基本执行单元,task 创建后会被提交给 tokio 调度器,我们只需要在使用的地方等待 task 执行完成即可。task 可能会被创建它的线程执行,也可能会交给别的运行时线程执行,总之,可以在不同的线程上被执行。

在 tokio 中 task 是非常轻量的,它们只需要单个分配和64字节的内存。

和 go 语言比较的话,task 相当于 GMP 调度中的 G 对象,也是一个非常轻量的对象,可以在不同的线程中被调用。

使用 tokio 来重构

上面也提到过,现在的代码只有 tokio 的表,没有 tokio 的里,现在我们重新使用 tokio 异步来实现一遍。通过简单的改造,终于实现了异步执行的效果,不禁感慨,确实不易啊。

fn learn_song() { 
    println!("learn song");
}

fn sing_song() { 
    println!("sing song");
}

async fn dance() {
    println!("dance");
}

#[tokio::main]
async fn main() {
    let f1 = tokio::spawn(async {
        learn_song();
    });
    let f2 = tokio::spawn(async {
        sing_song();
    });
    let f3 = tokio::spawn( 
        dance()
    );
    futures::join!(f1, f2, f3);
}

回顾

rust 异步调用的运行时是依赖三方实现的,在不引入 tokio 的情况下,并不会实现并发。同时,rust 中 tokio 的设计也类似于 go 语言中 GMP 的设计,异曲同工。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

openLayers实战(九):正多边形的绘制

最近在绘制各种图形&#xff0c;越来越乱&#xff0c;看了很多文章&#xff0c;启发了从最最基础的图形开始学习扩展。 遇到什么样的问题&#xff1f; import ol from "ol"; import { Draw } from "ol/interaction"; import { Vector as VectorSource } f…

React 之 Router - 路由详解

一、Router的基本使用 1. 安装react-router react-router会包含一些react-native的内容&#xff0c;web开发并不需要 npm install react-router-dom 2. 设置使用模式 BrowserRouter或HashRouter Router中包含了对路径改变的监听&#xff0c;并且会将相应的路径传递给子组件Bro…

学习笔记230804---restful风格的接口,delete的传参方式问题

如果后端提供的删除接口是restful风格&#xff0c;那么使用地址栏拼接的方式发送请求&#xff0c;数据放在主体中&#xff0c;后端接受不到&#xff0c;当然也还有一种可能&#xff0c;后端在这个接口的接参设置上是req.query接参。 问题描述 今天遇到的问题是&#xff0c;de…

BladeX多数据源配置

启用多租户数据库隔离&#xff0c;会默认关闭mybatis-plus多数据源插件的启动&#xff0c;从而使用自定义的数据源识别 若不需要租户数据库隔离只需要字段隔离&#xff0c;而又需要用到多数据源的情况&#xff0c;需要前往LauncherService单独配置 数据源切换失败 详情请看说明…

windows ipv4 多ip地址设置,默认网关跃点和自动跃点是什么意思?(跃点数)

文章目录 Windows中的IPv4多IP地址设置以及默认网关跃点和自动跃点的含义引言IPv4和IPv6&#xff1a;简介多IP地址设置&#xff1a;Windows环境中的实现默认网关跃点&#xff1a;概念和作用自动跃点&#xff1a;何时使用&#xff1f;关于“跃点数”如何确定应该设置多少跃点数&…

快速入门vue3组合式API

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 使用create-vue创建项目 熟悉项目目录和关键文件 组合式API setup选项 setup选项的写法和执行时机 s…

linux 安装 kibana

首先下载 kibana https://www.elastic.co/cn/downloads/kibana 然后上传到linux /usr/local 目录下解压安装 修改config/kibana.yml 配置文件&#xff0c;将elasticsearch.hosts 然后再nginx 中做一个端口映射&#xff0c;实现在浏览器中输入后xxxx:5602 nginx 可以将请求转发…

02-前端基础第二天-HTML5

01-HTML标签&#xff08;下&#xff09;导读 目标&#xff1a; 能够书写表格能够写出无序列表能够写出3~4个常用input表单类型能够写出下拉列表表单能够使用表单元素实现注册页面能够独立查阅W3C文档 目录&#xff1a; 表格标签列表标签表单标签综合案例查阅文档 02-表格标…

使用VSCode配置简单的vue项目

由于最近要使用的项目框架为前后端分离的&#xff0c;采用的是vue.jswebAPI的形式进行开发的。因为之前我没有接触过vue.js&#xff0c;也只是通过视频文档做了一些简单的练习。今天技术主管说让大家熟悉下VSCode开发vue&#xff0c;所以自己摸索了好久&#xff0c;才算是把简单…

矩阵乘法(C++ mpi 并行实现)

矩阵乘法有2种思路&#xff0c;我最先想到的是第一种思路&#xff0c;但是时间、空间复杂度都比较高。后面参考了一些资料&#xff0c;实现了第二种思路。 一、思路1&#xff1a;按行、列分块 矩阵乘法有一个很好的性质&#xff0c;就是结果矩阵的每个元素是不互相依赖的&…

AIGC音视频工具分析和未来创新机会思考

编者按&#xff1a;相较于前两年&#xff0c;2023年音视频行业的使用量增长缓慢&#xff0c;整个音视频行业遇到瓶颈。音视频的行业从业者面临着相互竞争、不得不“卷”的状态。我们需要进行怎样的创新&#xff0c;才能从这种“卷”的状态中脱离出来&#xff1f;LiveVideoStack…

ZooKeeper的应用场景(集群管理、Master选举)

5 集群管理 随着分布式系统规模的日益扩大&#xff0c;集群中的机器规模也随之变大&#xff0c;因此&#xff0c;如何更好地进行集群管理也显得越来越重要了。 所谓集群管理&#xff0c;包括集群监控与集群控制两大块&#xff0c;前者侧重对集群运行时状态的收集&#xff0c;后…

dolphinscheduler的僵尸任务清理和清理一直在运行的任务状态

dolphinscheduler的僵尸任务清理 界面操作不了的 只能去数据库更改状态或则删除掉 原因&#xff1a;海豚调度中有几百条僵尸任务&#xff0c; 界面怎么也删不掉&#xff0c;想从数据库中删除&#xff0c;开始查找从数据库删除的办法。 参考以下脚本&#xff0c;结合我库中僵尸…

Springboot 实践(10)spring cloud 与consul配置运用之服务的注册与发现

前文讲解&#xff0c;完成了springboot、spring security、Oauth2.0的继承&#xff0c;实现了对系统资源的安全授权、允许获得授权的用户访问&#xff0c;也就是实现了单一系统的全部技术开发内容。 Springboot是微服务框架&#xff0c;单一系统只能完成指定系统的功能&#xf…

Spring Boot中使用validator如何实现接口入参自动检验

文章目录 一、背景二、使用三、举例 一、背景 在项目开发过程中&#xff0c;经常会对一些字段进行校验&#xff0c;比如字段的非空校验、字段的长度校验等&#xff0c;如果在每个需要的地方写一堆if else 会让你的代码变的冗余笨重且相对不好维护&#xff0c;如何更加规范和优…

智汇云舟携三大系列产品亮相第68届中国安防工程商集成商大会

8月18日&#xff0c;由中国安全防范产品行业协会指导&#xff0c;永泰传媒主办的中国安防工程商&#xff08;系统集成商&#xff09;大会暨第68届中国安防新产品、新技术成果展示在广州盛大开幕。 来自华南各省、市安防协&#xff08;学&#xff09;会及全国安防工程商、系统集…

计算机视觉之三维重建(一)(摄像机几何)

针孔摄像机 添加屏障&#xff1a; 使用针孔(o光圈针孔摄像机中心)&#xff0c;实现现实与成像一对一映射&#xff0c;减少模糊。其中针孔与像平面的距离为f(焦距)&#xff1b;虚拟像平面位于针孔与真实物体之间&#xff0c;与像平面互为倒立关系。位置映射&#xff1a;利用相似…

【内网监控】通过cpolar实现远程监控

【内网监控】通过cpolar实现远程监控 文章目录 【内网监控】通过cpolar实现远程监控前言1. 在cpolar官网预留一个空白隧道2. 完成空白数据隧道&#xff0c;生成地址3. 设置空白隧道的出口4. 空白数据隧道的出口设置5. 获取公网地址6. 打开本地电脑“远程桌面”7. 打开Windows自…

jenkins gitlab 安装

目录 一 准备安装环境 二 安装gitlab软件 三 配置gitlab 四 重新加载配置启动gitlab 五 修改密码 五 创建用户组 一 准备安装环境 sudo yum update sudo yum install -y curl policycoreutils-python openssh-server安装 Postfix 邮件服务器&#xff0c;以便 Git…

2023年7月京东美妆护肤品小样行业数据分析(京东数据挖掘)

如今&#xff0c;消费者更加谨慎&#xff0c;消费决策也更加理性。在这一消费环境下&#xff0c;美妆护肤市场中&#xff0c;面对动辄几百上千的化妆品&#xff0c;小样或体验装无疑能够降低消费者的试错成本。由此&#xff0c;这门生意也一直备受关注。 并且&#xff0c;小样…