前置条件

  • 一个工作线程,负责执行异步函数
  • 异步函数Fun,无限阻塞,用不返回
  • 异步函数Func1,阻塞3秒
use tokio::{runtime, task};
use tokio::io::AsyncReadExt;
use tokio::time::{Duration, sleep};

async fn Fun() {
    println!("11");
    loop {
        // 一直阻塞
    }
}

async fn Func1() {
    println!("aa");
    std::thread::sleep(std::time::Duration::from_secs(3));
    println!("bb");
}


#[tokio::main]
async fn main() {
    let runtime = tokio::runtime::Builder::new_multi_thread().enable_all().worker_threads(1).build().unwrap();
    println!("start");
    runtime.spawn(Func1());
    runtime.spawn(Fun());
    println!("finish");
    loop {}
}

运行结果

start
finish
aa
bb
11

分析

由于我们只有一个工作线程,第一个调度的是Func1,先打印aa,然后用标准库自带的阻塞sleep模拟一个阻塞过程,3秒后输出bb。Func1结束后,工作线程再去调度执行Fun,打印出11后一直loop。

使用异步阻塞

将上诉代码中的sleep改为tokio库中的异步sleep后

use tokio::{runtime, task};
use tokio::io::AsyncReadExt;
use tokio::time::{Duration, sleep};

async fn Fun() {
    println!("11");
    loop {
        // 一直阻塞
    }
}

async fn Func1() {
    println!("aa");
    tokio::time::sleep(Duration::from_secs(3)).await;
    println!("bb");
}


#[tokio::main]
async fn main() {
    let runtime = tokio::runtime::Builder::new_multi_thread().enable_all().worker_threads(1).build().unwrap();
    println!("start");
    runtime.spawn(Func1());
    runtime.spawn(Fun());
    println!("finish");
    loop {}
}

运行结果

start
finish
aa
11

分析

执行Func1的第一行输出aa后执行sleep,是个阻塞函数,tokio立刻先调度执行Fun,因为阻塞在这没意义,等3秒后再调度回来,结果调度过去执行Fun后一直阻塞没法调度回来了

总结

异步提升并发的关键在于,相对于传统的阻塞浪费CPU片段,异步会把这个CPU片段用于执行其他任务,不会白白浪费时间在等待