Rust: Rust 异步入门 (作者洋芋,来自Rust语言中文社区)

2023-05-16

【Rust每周一知】Rust 异步入门
原创 洋芋 Rust语言中文社区 前天

这是一篇博文翻译,略有删减,整理代码方便统一阅读,Github链接:https://github.com/lesterli/rust-practice/tree/master/head-first/async-primer。

原文在2月11号的【Rust日报】中给大家推荐过, 原文链接: https://omarabid.com/async-rust

本文并不全面介绍Rust异步主题。如果对新的async/await关键字Futures感到疑惑,并且对Tokio的用途很感兴趣,那么到最后应该会不再毫无头绪。

Rust异步技术是Rust领域的新热点,它被誉为Rust的重要里程碑,特别适合开发高性能网络应用程序的人们。

让我们从头开始。
什么是异步?

关于Async,我给一个简短的版本:如果有一个处理器,想同时执行(类似)两项任务,将如何做?解决方案是先运行第一个任务,然后切换并运行第二个任务,然后再切换回去,依此类推,直到完成两个任务。

如果想给人以计算机同时运行两个任务的感觉(即多任务处理),则此功能很有用。另一个用例是IO操作。当程序等待网络响应时,CPU处于空闲状态。这是切换到另一个任务的理想时间。

那么我们如何编写异步代码?

首先,让我们从一些同步代码开始。
同步代码

让我们做一个简单的程序,该程序读取两个文件:file1.txt和file2.txt。我们从file1.txt开始,然后移至file2.txt。

我们将程序分为两个文件:main.rs和file.rs。file.rs有一个函数:read_file,在main.rs中,用每个文件的路径为参数调用此函数。参见下面代码:

// sync-example/src/file.rs

use std::fs::File;
use std::io::{self, Read};

pub fn read_file(path: &str) -> io::Result<String> {
    let mut file = File::open(path)?;
    let mut buffer = String::new();
    file.read_to_string(&mut buffer)?;
    Ok(buffer)
}

// sync-example/src/main.rs

use std::io;

mod file;

fn main() -> io::Result<()> {
    println!("program started");

    let file1 = file::read_file("src/file1.txt")?;
    println!("processed file 1");

    let file2 = file::read_file("src/file2.txt")?;
    println!("processed file 2");

    dbg!(&file1);
    dbg!(&file2);

    Ok(())
}

使用cargo run编译并运行程序。该程序应该毫无意外地运行,但是请确保已在src文件夹中放置了两个文件(file1.txt和file2.txt)。

program started
processed file 1
processed file 2
[src/main.rs:14] &file1 = "file1"
[src/main.rs:15] &file2 = "file2"

到目前为止,一切都很好。如果需要在处理file2.txt之前先处理file1.txt,那么这是唯一的方法。但是有时不必关心每个文件的处理顺序。理想情况下,希望尽快处理文件。

在这种情况下,我们可以利用多线程。
多线程方法

为此,我们为每个函数调用运行一个单独的线程。由于我们使用的是多线程代码,并且如果要访问线程外部的文件内容,则必须使用Rust提供的同步原语之一。

这将如何影响代码:file.rs将保持不变,因此这已经是一件好事了。在main.rs中,我们需要初始化两个RwLock;这些将稍后在线程中用于存储文件内容。

然后,我们运行一个无限循环,尝试读取这两个变量的内容。如果这些变量不为空,则我们知道文件处理(或读取)已完成。 (这意味着文件不应为空;否则,我们的程序将错误地保持等待状态。另一种方法是使用Option并检查Option是否为None)。

此代码需要crate lazy_static。

// multi-example/src/main.rs

use std::io;
use std::sync::RwLock;
use std::thread;

use lazy_static::lazy_static;

mod file;

// A sync primitive that allows to read/write from variables between threads.
// we declare the variables here, this requires the lazy_static crate
lazy_static! {
    static ref FILE1: RwLock<String> = RwLock::new(String::from(""));
    static ref FILE2: RwLock<String> = RwLock::new(String::from(""));
}

fn main() -> io::Result<()> {
    println!("program started");

    let thread_1 = thread::spawn(|| {
        let mut w1 = FILE1.write().unwrap();
        *w1 = file::read_file("src/file1.txt").unwrap();
        println!("read file 1");
    });

    println!("Launched Thread 1");

    let thread_2 = thread::spawn(|| {
        let mut w2 = FILE2.write().unwrap();
        *w2 = file::read_file("src/file2.txt").unwrap();
        println!("read file 2");
    });

    println!("Launched Thread 2");

    let mut rf1: bool = false;
    let mut rf2: bool = false;

    loop {
    	// read()
        let r1 = FILE1.read().unwrap();
        let r2 = FILE2.read().unwrap();

        if *r1 != String::from("") && rf1 == false {
            println!("completed file 1");
            rf1 = true;
        }

        if *r2 != String::from("") && rf2 == false {
            println!("completed file 2");
            rf2 = true;
        }
    }

    Ok(())
}

有趣的是,如果我们有一个非常大的file1.txt,我们将得到一个奇怪的输出。首先处理第二个文件(读取文件2);但在我们的循环内部,该程序似乎阻塞并等待第一个文件。

program started
Launched Thread 1
Launched Thread 2
read file 2
read file 1
completed file 1
completed file 2

多线程可能有点棘手,因为我们必须考虑可能阻塞的原子操作。我们使用read函数来解锁我们的变量,并且文档对这种行为发出警告。

使用共享的读取访问权限锁定此rwlock,阻塞当前线程,直到可以获取它为止。

幸运的是,有一个try_read函数,如果无法获取锁,则返回Err。

尝试使用共享的读取访问权限获取此rwlock。

如果此时不能授予访问权限,则返回Err。 否则,将返回RAII保护,当该保护被删除时,该保护将释放共享访问。

在第二次尝试中,我们使用try_read并忽略返回的Errs,因为它们应该表示我们的锁正忙。这有助于将程序移至下一个变量,并处理先准备好的变量。

// multi-example/src/main.rs

...
    loop {
    	// try_read()
        let r1 = FILE1.try_read();
        let r2 = FILE2.try_read();

        match r1 {
            Ok(v) => {
                if *v != String::from("") && rf1 == false {
                    println!("completed file 1");
                    rf1 = true;
                }
            }
            // If rwlock can't be acquired, ignore the error
            Err(_) => {}
        }

        match r2 {
            Ok(v) => {
                if *v != String::from("") && rf2 == false {
                    println!("completed file 2");
                    rf2 = true;
                }
            }
            // If rwlock can't be acquired, ignore the error
            Err(_) => {}
        }
    }

现在执行方式有所不同。如果file1.txt比file2.txt大得多,则应首先处理第二个文件。

program started
Launched Thread 1
Launched Thread 2
read file 2
completed file 2
read file 1
completed file 1

多线程的局限性

如果我们已经有多线程,为什么我们需要异步?有两个主要优点:性能和简单性。产生线程很昂贵;从以上内容可以得出结论,编写多线程代码可能会变得非常复杂。
异步,关键字

Rust的重点是使编写Async代码尽可能简单。只需要在函数声明之前添加async/await关键字即可使代码异步:函数声明前async,解析异步函数await。

这听起来很不错。试一试吧。

use std::fs::File;
use std::io::{self, Read};

pub async fn read_file(path: &str) -> io::Result<String> {
    let mut file = File::open(path)?;
    let mut buffer = String::new();
    file.read_to_string(&mut buffer)?;
    Ok(buffer)
}

use std::io;

mod file;

fn main() -> io::Result<()> {
    let r1 = file::read_file("src/file1.txt");
    let r2 = file::read_file("src/file2.txt");

    let f1 = r1.await;
    let f2 = r2.await;

    dbg!(f1);
    dbg!(f2);

    Ok(())
}

但是这不能通过编译,await仅在异步块或函数中可用。如果我们尝试运行此代码,则编译器将引发此错误。

error[E0728]: `await` is only allowed inside `async` functions and blocks
 --> src/main.rs:9:14
  |
5 | fn main() -> io::Result<()> {
  |    ---- this is not `async`
...
9 |     let f1 = r1.await;
  |              ^^^^^^^^ only allowed inside `async` functions and blocks

我们可以使main函数异步吗?不幸的是,事情并非如此简单。我们得到另一个错误。

error[E0277]: `main` has invalid return type `impl std::future::Future`
 --> src/main.rs:5:20
  |
5 | async fn main() -> io::Result<()> {
  |                    ^^^^^^^^^^^^^^ `main` can only return types that implement `std::process::Termination`
  |
  = help: consider using `()`, or a `Result`

但是,错误消息有点令人着迷。似乎async关键字使我们的函数返回Future而不是声明的类型。

异步函数的返回类型是Future(确切地说是实现Future特性的闭包)。

那await呢?await在整个Future中循环直至完成。但是,还有另外一个谜团:Rust无法自解析Future。我们需要一个执行器来运行此异步代码。
什么是执行器?

如果回顾一下我们的多线程示例,会注意到我们使用循环来检测何时处理文件。这很简单:无限循环直到变量中包含某些内容,然后执行某些操作。如果读取两个文件,我们可以通过跳出循环来改善这一点。

一个异步执行器是循环。默认情况下,Rust没有任何内置的执行程序。有许多异步运行时;async-std和Tokio是最受欢迎的。运行时的工作是轮询异步函数(Future),直到它们最终返回一个值。
一个简单的执行器

crate futures有一个非常基本的执行器,并且具有将两个Future连接的函数。让我们试一试。

以下代码使用crate futures版本0.3.4。

// async-example/src/main.rs

use futures::executor::block_on;
use futures::join;
use std::io;

mod file;

fn main() -> io::Result<()> {

    println!("Program started");

    // Block on the final future
    block_on(load_files());

    Ok(())
}

async fn load_files() {
    // Join the two futures together
    join!(load_file_1(), load_file_2());
}

async fn load_file_1() {
    let r1 = file::read_file("src/file1.txt").await;
    println!("file 1 size: {}", r1.unwrap().len());
}

async fn load_file_2() {
    let r2 = file::read_file("src/file2.txt").await;
    println!("file 2 size: {}", r2.unwrap().len());
}

为了验证异步性,将一堆数据转储到file1.txt中。

Program started
file 1 size: 5399
file 2 size: 5

不幸的是,这看起来(确实)第一个文件函数再次阻塞了。
那么异步到底是什么?

与多线程类似,异步编程中也有一些陷阱和问题。事实是,async关键字不会神奇地使代码异步;它只是使函数返回Future。仍然必须繁重地安排代码执行时间。

这意味着函数必须迅速返回尚未准备就绪的状态,而不是被困在进行计算的过程中。在我们的情况下,阻塞是特定在File::Open和file.read_to_string处发生的。这两个函数不是异步的,因此会阻止执行。

我们需要创建这两个函数的异步版本。幸运的是,一些使用async-std的人做了工作,将Rust中的std库重写为异步版本。
使用async-std的文件IO

我们唯一要做的更改是将我们的std导入替换为async_std。

对于以下示例,我们使用crate async-std版本1.5.0。

// async-example/src/file.rs

// We use async_std instead of std, it's that simple.
use async_std::io;
use async_std::fs::File;
use async_std::prelude::*;

pub async fn read_file(path: &str) -> io::Result<String> {
    let mut file = File::open(path).await?;
    let mut buffer = String::new();
    file.read_to_string(&mut buffer).await?;
    Ok(buffer)
}

main.rs中的代码保持不变;该程序仍使用crate futures中的block_on执行程序。

编译并运行程序。(确保有一个大的file1.txt)

Program started
file 2 size: 5
file 1 size: 5399

最后!程序首先快速处理file2.txt,然后移至file1.txt。

让我们回顾一下到目前为止所学到的东西:

async使我们的函数返回Future。

运行我们的Future需要一个运行时。

运行时检查Future是否准备就绪;并在就绪时返回其值。

总结

在这篇文章中,我们介绍了同步代码,多线程代码,Rust中的一些异步术语,async-std库和简单的Future实现。实际上,这是一个"轻量级"的介绍,为简洁起见,省略了许多细节。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Rust: Rust 异步入门 (作者洋芋,来自Rust语言中文社区) 的相关文章

  • 一位 JavaScript 铁杆粉眼中的 Rust

    以下为译文 xff1a 我使用 Rust 编写了一些小工具 xff0c 而且觉得很有乐趣 我的日常工作需要大量使用 JavaScript xff0c 而 Rust 给我一种非常熟悉的感觉 xff0c 因此我决定尝试一下Rust 但与此同时
  • 树莓派3B+搭配Buster版本系统进行红外遥控开发

    一 配件清单 树莓派 xff1a 3B 43 系统版本 xff1a Buster红外接收器 xff1a VS1838B 红外遥控器 xff1a 未知型号 xff08 标有ar mp3字样 xff09 杜邦线若干 二 线路组合准备 根据网上查
  • Rust生态技术栈

    文章目录 Rust开发生态 开发整理 20230106更新 1 日志记录1 1 simple logger1 2 env logger 2 输入 输出3 String类型的match4 print 输出无效问题5 线程6 Excel读取7
  • Rust GUI方案调研

    GUI库方案 xff1a QT xff1a qt功能强大 xff0c 稳定 xff0c 如果功能比较复杂 xff0c 可以考虑qt绑定 orbtk xff1a rust语言编写的操作系统redox项目的GUI方案 xff0c 完全使用rus
  • windows远程Ubuntu(xrdp+vnc)步骤及问题解决方案(ip设置)

    首先将计算机连入相应的路由器 xff0c 登陆账号即可上网 xff0c 下面部分引用了blog xff1a http zhouxiaowei1120 github io Blogs 20160407 html 其中第 xff08 5 xff
  • Ubuntu/debian 中更改桌面的路径/位置

    虚拟机debian系统中安装好vmware tool 后 xff0c 系统的桌面变为了主目录 修改如下 xff1a vi config user dir dirs 把其中的 XDG DESKTOP DIR 61 HOME 改成如下 XDG
  • 八数码问题的可解性

    对于给定八数码棋局的初始状态 xff0c 我们的目标是通过交换空格与其相邻棋子使棋盘达到目标状态 其中 xff0c 游戏规则是只能交换空格与其上下左右四个方向的相邻棋子 假设棋局目标状态为如下形式 xff1a xff08 A B C D E
  • Powershell远程错误整理

    xfeff xfeff 错误现象 权限不够 xff1a 执行Set WSManQuickConfig Enable PSRemoting会报错 xff0c 用Get Item WSMan 检查时会发现其子项无法访问 xff0c 用reged
  • vs2013编译错误error: MSB8020,一招解决这个错误

    错误提示 xff1a error MSB8020 The build tools for v140 Platform Toolset 61 39 v140 39 cannot be found To build using the v140
  • CSP考试 2016年04月第3题 路径解析 C++实现

    表示本目录 xff0c 例如 d1 f1 指定的就是 d1 f1 如果有多个连续的 出现 xff0c 其效果等同于一个 绝对路径 xff1a 以 符号开头 xff0c 表示从根目录开始构建的路径 相对路径 xff1a 不以 符号开头 xff
  • bash环境中的通配符和特殊符号的简单介绍

    Bash环境中可以用通配符 wildcard 来更好的对数据进行处理 下面介绍一些常用的通配符 符号 意义 代表0个到无穷多个任意字符 代表一定有一个任意字符 代表一定有一个在中括号内得字符 非任意字符 例如 abcd 代表一定有一个字符
  • spring boot自定义注解拦截接口

    自定义注解 xff0c 拦截接口请求 maven依赖管理 span class token generics function span class token punctuation lt span dependency span cla
  • nacos与spring cloud

    前言 从微服务兴起后 xff0c 服务治理问题已经成为其最大问题 起初有eureka xff0c zookeeper consul同台竞争 xff0c 本人也使用过eureka 43 config server作为注册中心和配置中心 xff
  • nacos与sentinel

    sentinel 高可用流控 sentinel 是阿里开源的一款系统流控系统 xff0c 可以在线配置本系统请求访问请求控制 软件下载 源码 运行版 运行 nohup java jar sentinel dashboard 1 7 2 ja
  • jenkins 结合ssh 实现多服务器部署jar包

    jenkins使用手册 简介 官网介绍 xff1a Jenkins是开源的CI CD xff08 持续集成 持续交付 xff09 软件领导者 有如下几个优点 xff1a 简单安装配置简单丰富的插件 超过1000个插件扩展性分布式 一 软件下
  • feign调用初始化问题

    最近使用spring cloud微服务遇到一个问题 xff0c 微服务之间使feign第一次调用时会非常的耗时 xff0c 一个请求如果调用链长的情况下 xff0c 接口返回可能会达到10s以上 xff0c 这是一个正常系统都不能容忍的 基
  • VScode 无法下载Go工具解决方案

    使用七牛云代理下载所需要的工具依赖 xff0c 具体配置请查看 说明 https goproxy cn
  • Sonar代码质量管理服务搭建并导入java项目

    1 软件下载 7 4 2 软件配置 打开解压后文件conf sonar properties mysql版本必须大于5 6小于8 0 MySQL gt 61 5 6 amp amp lt 8 0 sonar jdbc url 61 mysq
  • java join方法实现线程的串行

    java实现多线程之间串行执行 xff0c 网上也有很多的教程 在这里我主要说的是 xff1a java Thread类的join函数 xff0c 先贴代码吧 xff1a package cn com fhz thread Created
  • Windows上Rust所依赖的msvc到底怎么装?

    在Windows上面安装Rust的开发环境 xff0c 看起来颇具挑战性 我们会被告知需要安装一个名叫Microsoft Visual Studio C 43 43 build tools的编译工具 xff0c 并被给到一个官方链接 然而

随机推荐

  • Word 转 Markdown

    1 Pandoc 工具将 Word 文档转为 Markdown 可以借助 Pandoc 工具将 Word 文档转为 Markdown xff0c 例如 xff0c 此处将 README docx 转成 README md xff0c 命令如
  • c语言现代方法 chapter20自学笔记

    如果编写程序需要用到数据在内存中如何存储 xff0c 那么除非必要 xff0c 否则不用 xff0c 如果用 xff0c 那么集中在程序中的某个模块 xff0c 不要分散在各处 20 1 移位运算符 c语言提供了6个位运算符 这些运算符可以
  • 查看python源码之jieba安装

    Python 2 x 下的安装 全自动安装 xff1a easy install jieba 或者 pip install jieba 半自动安装 xff1a 先下载http pypi python org pypi jieba xff0c
  • 在Ubuntu Linux上搭建go语言环境

    一 安装VMware Tools 1 在刚装好的ubuntu linux上 xff0c 如果没有安装VMware Tools xff0c 那么我们就要先安装它 打开我们的ubuntu linux xff0c 然后点击左上角的虚拟机 xff0
  • Linux nohup实现后台运行程序及查看(nohup与&)

    1 后台执行 一般运行linux上的程序都是执行 sh 文件 xff08 sh文件 xff09 xff0c 那如果不影响当前CMD窗口的操作 xff0c 需要后台运行怎么办呢 xff1f 这时就需要借助 nohup 和 amp 命令来实现
  • CA 认证过程及 https 实现方法

    CA 认证过程 CA 概述 xff1a Certificate Authority 的缩写 xff0c 通常翻译成认证权威或者认证中心 xff0c 主要用途是为用户发放数字证书 CA 认证的流程和公安局派出所颁发身份证的流程一样 认证中心
  • C++代码详解:string的赋值与C风格字符串转换

    C 43 43 代码详解 string的赋值与C风格字符串转换 61 61 61 61 61 string简介 61 61 61 61 61 string是C 43 43 里默认的字符串容器 xff0c 用于代替C风格的字符串指针与字符串数
  • nginx 基本配置

    一篇比较好的参考文 https www digitalocean com community tutorials how to install nginx on ubuntu 18 04 1 在 Ubuntu 上安装 Nginx sudo
  • 解决[[NSFileManager defaultManager] contentsOfDirectoryAtPath 方法获取不到数据的bug

    在说这个问题之前 必须先解释一下 我们在引入工程的时候 xcode会给我们3个选项 1 Copy items if needed 主要是说明 xff0c 是否要将文件拷贝到工程所在目录 如果不选中 xff0c 而且该库文件不在工程目录下 x
  • GitLab+Jenkins集群+docker CICD集成

    前言 最近部门进行CICD架构升级将引入k8s编排docker容器 借此机会梳理下目前应用部署发布方式 当前架构是我刚到公司时基于gitlab 43 jenkins 43 docker 43 nexus搭建 引入K8S后将调整pod yam
  • 10个轻松上手制作的Arduino项目

    创建Arduino项目可以给您带来很大的满足感 xff0c 但很多时候初学者不知道从哪里开始 启动创建项目时需要考虑很多事情 xff0c 如果您没有制作的经验 xff0c 那可能会令人困惑 正是因为这个原因 xff0c 我们为初学者收集到1
  • 7个基于Arduino的神奇项目!

    创客人数的规模正在增加 xff0c 从当地的超市到大城市繁忙的办公室 xff0c 到处都可以找到他们 xff0c 这些地方可能会有一个或两个创客 xff0c 或许更多 xff0c 在制定他们的下一个设计 xff0c 或者可能渴望找到一个前现
  • 用if语句如何检测一个变量是否存在。

    例如我们用if语句检测一下num这个变量是否存在 若我们直接写 xff1a var night 61 34 34 if num night 61 34 yes 34 console log night 注意 xff01 xff01 1 如果
  • nodejs中的__filename和__dirname的使用说明

    在node js开发中 xff0c 有时候需要获取一些环境变量 xff0c 其中 filename和 dirname 是两个有用的环境变量 dirname 获取的是调用 dirname的脚本文件本身的绝对路径 xff0c 不是启动脚本文件的
  • LPC1768 -- RTC实时时钟

    RTC是当下设备中比较普遍的一个部件 xff0c 很多设备都需要查看时间 RTC实时时钟已经在很多的单片机中集成 xff0c 以前还要专门的时钟芯片 xff0c 现在Cortex M3内核都包括了这个部件了 和以前NXP的ARM7内核不同的
  • Ubuntu Apache2配置SSL证书

    一 Ubuntu下的Apache2 1 安装Apache2 sudo apt get install apache2 默认站点在 var www 配置文件在 etc apache2 日志在 var log apache 启动脚本是 etc
  • 时间机器

    让我们回到过去的是回忆 让我们勇敢前行的是期望 致我最爱的电影 时间机器
  • pyqt5 Qthread事件 进度条 案例

    pyqt5 Qthread事件 进度条 案例 代码 xff1a from PyQt5 import QtWidgets span class token punctuation span QtCore import sys from PyQ
  • CString 的成员函数详解

    CString 的成员函数详解 CSTRING的成员函数详解 typedef ATL CStringT lt TCHAR StrTraitMFC DLL lt TCHAR gt gt CString CString的构造函数 xff1a 1
  • Rust: Rust 异步入门 (作者洋芋,来自Rust语言中文社区)

    Rust每周一知 Rust 异步入门 原创 洋芋 Rust语言中文社区 前天 这是一篇博文翻译 xff0c 略有删减 xff0c 整理代码方便统一阅读 xff0c Github链接 xff1a https github com lester