首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >什么时候应该使用tokio::join!() over tokio::sp产物()?

什么时候应该使用tokio::join!() over tokio::sp产物()?
EN

Stack Overflow用户
提问于 2021-10-19 23:36:28
回答 3查看 8.4K关注 0票数 15

假设我想和Tokio同时下载两个网页..。

要么我可以用tokio::spawn()实现这一点

代码语言:javascript
复制
async fn v1() {
    let t1 = tokio::spawn(reqwest::get("https://example.com"));
    let t2 = tokio::spawn(reqwest::get("https://example.org"));
    let (r1, r2) = (t1.await.unwrap(), t2.await.unwrap());
    println!("example.com = {}", r1.unwrap().status());
    println!("example.org = {}", r2.unwrap().status());
}

或者我可以用tokio::join!()实现这一点

代码语言:javascript
复制
async fn v2() {
    let t1 = reqwest::get("https://example.com");
    let t2 = reqwest::get("https://example.org");
    let (r1, r2) = tokio::join!(t1, t2);
    println!("example.com = {}", r1.unwrap().status());
    println!("example.org = {}", r2.unwrap().status());
}

在这两种情况下,这两个请求同时发生。但是,在第二种情况下,这两个请求在相同的任务中运行,因此在同一个线程上运行。

所以,我的问题是:

  • tokio::join!()tokio::spawn()有优势吗?
  • 如果是,在什么情况下?(它不需要下载网页)

我猜产生一项新任务的开销很小,但这是吗?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-10-20 03:01:59

我通常会从另一个角度来看待这个问题;为什么我要在tokio::join上使用tokio::join?产生一个新的任务比加入两个期货有更多的限制,'static的要求可能是非常恼人的,因此不是我的选择。

除了产生任务的成本,我想这是相当微不足道的,还有当它完成的时候发出信号的成本。我也认为这是边际的,但是您必须在您的环境和异步工作负载中度量它们,看看它们是否真的有影响。

但是你是对的,使用两个任务最大的好处是他们有机会并行工作,而不仅仅是同时工作。但是另一方面,async最适合于I/O绑定的工作负载,在那里有大量的等待,并且取决于您的工作负载,这种缺乏并行性可能不会产生很大的影响。

总之,tokio::join是一个更好和更灵活的使用,我怀疑技术上的差异会对性能产生影响。但一如既往:量入为出!

票数 7
EN

Stack Overflow用户

发布于 2021-10-20 02:51:21

差异将取决于您如何配置运行时。tokio::join!将在同一任务中并发运行任务,而tokio::spawn!则为每个任务创建一个新任务。

在单线程运行时,它们实际上是相同的。在多线程运行时,两次使用tokio::spawn! (类似于),可能使用两个单独的线程。

来自文档 for tokio::join!

通过在当前任务上运行所有异步表达式,这些表达式能够同时运行,但不能在并行中运行。这意味着所有表达式都运行在同一个线程上,如果一个分支阻塞该线程,则所有其他表达式都将无法继续。如果需要并行性,则使用tokio::spawn生成每个异步表达式,并将连接句柄传递给join!

对于IO绑定的任务,比如下载网页,您不会注意到其中的区别;大部分时间将用于等待数据包,并且每个任务都可以有效地交织它们的处理。

当任务受到更多的CPU限制,并且可能相互阻塞时,使用tokio::spawn!

票数 9
EN

Stack Overflow用户

发布于 2021-10-20 03:46:45

@kmdreko的回答很棒,我想补充一些细节!

如前所述,使用tokio::spawn有一个'static需求,因此以下代码段不编译:

代码语言:javascript
复制
async fn v1() {
    let url = String::from("https://example.com");
    let t1 = tokio::spawn(reqwest::get(&url)); // `url` does not live long enough
    let t2 = tokio::spawn(reqwest::get(&url));
    let (r1, r2) = (t1.await.unwrap(), t2.await.unwrap());
}

但是,与tokio::join!对应的代码段确实编译了:

代码语言:javascript
复制
async fn v2() {
    let url = String::from("https://example.com");
    let t1 = reqwest::get(&url);
    let t2 = reqwest::get(&url);
    let (r1, r2) = tokio::join!(t1, t2);
}

而且,这个答案让我对产生新任务的成本感到好奇,因此我编写了以下简单的基准:

代码语言:javascript
复制
use std::time::Instant;

#[tokio::main]
async fn main() {
    let now = Instant::now();
    for _ in 0..100_000 {
        v1().await;
    }
    println!("tokio::spawn = {:?}", now.elapsed());

    let now = Instant::now();
    for _ in 0..100_000 {
        v2().await;
    }
    println!("tokio::join! = {:?}", now.elapsed());
}

async fn v1() {
    let t1 = tokio::spawn(do_nothing());
    let t2 = tokio::spawn(do_nothing());
    t1.await.unwrap();
    t2.await.unwrap();
}

async fn v2() {
    let t1 = do_nothing();
    let t2 = do_nothing();
    tokio::join!(t1, t2);
}

async fn do_nothing() {}

在发布模式下,我在我的macOS笔记本电脑上获得了以下输出:

代码语言:javascript
复制
tokio::spawn = 862.155882ms
tokio::join! = 369.603µs

编辑:这个基准在很多方面都有缺陷(见注释),所以不要依赖它来获取具体的数字。然而,关于产卵比加入2项任务更昂贵的结论似乎是正确的。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69638710

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档