不用再担心阻塞了:受go启发的新async Std运行时

不用再担心阻塞了:受Go启发的新async-std运行时

斯捷潘·格拉维纳Stjepan Glavina)发表于2019年12月16日在

async-std是Rust标准库向async/await新世界发展的成熟和稳定的端口,旨在使异步编程变得简单,高效,无忧且无错误。

我们在8月16日(也就是4个月前)宣布了async-std。我们最初发布的重点是为用户提供稳定可靠的API,以基于Rust标准库为模型构建异步应用程序。它带有许多创新的实现:JoinHandle基于任务的API和单一分配任务的第一个实现。

今天,我们将介绍新的async-std运行时。它具有许多改进,但主要新闻是它消除了并发程序中的错误和性能问题的主要来源:意外阻塞。

综上所述:

新的任务调度算法和阻止策略是Go运行时对Rust使用的思想的改编。

阻塞的问题

直到今天,在Rust中编写异步程序时,经常遇到挫折的可能是在运行任务时意外 阻塞执行程序线程。为避免该问题,async-std用于提供不稳定的spawn_blocking功能,该功能将潜在的阻塞工作移至特殊的线程池上,以便执行程序线程可以保持进度。

但是,spawn_blocking这不是防弹解决方案,因为我们必须记住每次希望阻止时都要手动调用它。但是,甚至很难可靠地预测可以阻止哪种代码。程序员必须仔细分离异步代码和阻塞代码,这是一个臭名昭著的问题,在博客文章“ 什么颜色是您的功能 ”中进行了讨论。

当您考虑可能会阻塞当前执行程序线程的操作包括普通的昂贵计算(例如,转换图像或对大量数据进行排序)时,代码分离会变得更加困难。相反,有时我们会悲观地假设一些代码块,而实际上这可能会很快,这意味着spawn_blocking即使没有必要,我们也会付出代价。

使用新的async-std运行时,这样的困难已成为过去:如果任务执行时间过长,运行时将通过产生新的执行程序线程来接管当前线程的工作,从而自动做出反应。这种策略消除了分离异步代码和阻塞代码的需要。

一个具体的例子

为了说明所有这些在实践中的含义,让我们std::fs::read_to_string以阻塞操作为例。该异步版本的它用来实现如下:

async fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> {
    let path = path.as_ref().to_owned();
    spawn_blocking(move || std::fs::read_to_string(path)).await
}

请注意两个关键事项:

新的运行时减轻了您的这些麻烦,并允许您直接在async函数内部执行阻塞操作:

async fn read_to_string(path: impl AsRef<Path>) -> io::Result<String> {
    std::fs::read_to_string(path)
}

运行时将测量执行阻塞操作所需的时间,如果需要一段时间,则会自动生成一个新线程并替换旧的执行程序线程。这样,仅当前任务在操作上被阻止,而不是整个执行程序线程或整个运行时被阻止。如果阻塞操作很快,我们就不会产生新的线程,因此不会造成任何额外的开销。

如果您仍要确保操作在后台运行并且也不阻塞当前任务,则可以简单地生成一个常规任务并在其中执行阻塞工作:

async fn read_to_string(path: impl AsRef<Path>) -> io::Result<String> {
    let path = path.as_ref().to_owned();
    spawn(async move { std::fs::read_to_string(path) }).await
}

请注意使用spawn代替spawn_blocking

Web框架通常需要异步I / O,同时每个请求都要执行大量工作。新的运行时,您可以使用无畏同步库像dieselrayon在任何async-std应用程序。

基准测试

在我们的初始测试中,新的调度程序比旧的调度程序性能更好,同时仍保持较小且易于理解。

以下基准测试在两个EC2实例上运行。一个minihttp基于旧的运行时(服务器master分支)和新的运行时(new-scheduler分支)上m5a.8xlarge实例上运行,而WRK(一个基准测试工具)上的单独m5a.16xlarge实例中运行。

该基准测试具有三种不同的方案,并向wrk传递了不同的参数。Option -t配置线程数,-c配置TCP连接数,并-d配置基准测试的持续时间(以秒为单位)。有关如何运行基准测试的更多详细信息,请参见自述文件

不同基准的图表。 wrk -t1 -c50 -d10:新的调度程序快2倍。 wrk -t10 -c50 -d10:新的调度程序快5倍。 wrk -t1 -c500 -d10:新的调度程序快15倍

一般而言,新的运行时更快,并且可以更好地扩展以利用可用资源。

小而有据可查的

新的运行时很小,不使用unsafe并且已记录在案。请查看以了解其工作原理。随意在拉动要求上提问!仍有许多优化机会,我们将继续在博客中介绍这些细节!

尝试一下

要在发布前试用新的调度程序,请按Cargo.toml以下方式进行修改:

async-std = { git = 'https://github.com/stjepang/async-std', branch = 'new-scheduler' }

请报告您的经验-并报告潜在的错误!

摘要

新的async-std运行时减轻了程序员将阻塞代码与异步代码隔离的负担。您完全不必再为此担心。

当多线程没有带来任何好处时,新运行时的自适应特性使其可以使用更少的资源。这样可以提高CLI工具的性能,并降低Web服务器的延迟。同时,运行时将扩展以在繁重的工作负载中使用所有可用资源。

所有这些更改使编写异步程序变得更加容易,同时使它们更高效,更可靠!

我们要感谢async-std的所有贡献者,无论大小,新的和长期的,以及所有库作者为Rust异步生态系统构建了出色的东西!

Powered by Jekyll and Theme by solid

本站总访问量