rust异步

1.异步运行时分类

运行时 特点 推荐场景
trpl 简单、轻便,便于演示 async/await 教学/演示,非正式运行时
*Tokio 完整生态、多线程支持 网络服务、数据库、Web 应用
async-std std 风格易用 CLI 工具、轻量服务
smol 极简依赖,体积小 嵌入式、工具类程序、小型服务
embassy no_std 支持,异步调度 嵌入式设备(如 ESP32)
glommio io_uring 高性能支持 Linux 高并发服务器(更底层)
monoio 类似 glommio,更现代 Linux 微服务、RPC 框架等

2.Tokio的异步运行时

2-1 创建运行时Runtime

tokio提供了两种工作模式的runtime

说明

1.启动了一个事件循环线程池(默认CPU核数的线程)。
2.初始化了调度器、任务队列、计时器等一整套机制。

创建Runtime就意味着调度器和线程池已启动,spawn立即可用,不需要先block_on()。

  • let rt = Runtime::new():立刻创建并启动Runtime。

  • rt.spawn():立刻生效,把任务交给后台工作线程。

  • block_on():不是启动Runtime,而是同步阻塞当前线程执行某个Future。

let rt = tokio::runtime::Runtime::new().unwrap();

2-1-1 单一线程的Runtime

use tokio;

fn main() {
  // 使用new_current_thread创建一个单一线程
  let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
  std::thread::sleep(std::time::Duration::from_secs(10));
}

线程数

test_async对应 Cargo.toml 中的package.name

ps aux | grep -v grep |grep test_async| awk '{print $2}'|xargs ps -M

USER     PID   TT   %CPU STAT PRI     STIME     UTIME COMMAND
lxd670 58578 s001    0.0 S    31T   0:00.07   0:00.10 target/debug/test_async

2-1-2 多线程(线程池)的Runtime

这里的所说的线程是Rust线程,而每一个Rust线程都是一个OS线程

tokio::runtime创建

默认CPU核心数 + 1 个主线程

use tokio;

fn main() {
  // 创建runtime
  let rt = tokio::runtime::Runtime::new().unwrap();
  std::thread::sleep(std::time::Duration::from_secs(10));
}
线程数

8个(cpu 数) + 1 个主线程

ps aux | grep -v grep |grep test_async| awk '{print $2}'|xargs ps -M

USER     PID   TT   %CPU STAT PRI     STIME     UTIME COMMAND
lxd670 58776 s001    0.0 S    31T   0:00.07   0:00.09 target/debug/test_async
       58776         0.0 S    31T   0:00.00   0:00.00 
       58776         0.0 S    31T   0:00.00   0:00.00 
       58776         0.0 S    31T   0:00.00   0:00.00 
       58776         0.0 S    31T   0:00.00   0:00.00 
       58776         0.0 S    31T   0:00.00   0:00.00 
       58776         0.0 S    31T   0:00.00   0:00.00 
       58776         0.0 S    31T   0:00.00   0:00.00 
       58776         0.0 S    31T   0:00.00   0:00.00 
查看 cup 数

cargo add num_cpus添加

use num_cpus;

fn main() {
    let cpus = num_cpus::get();
    println!("Running {} CPUs", cpus);
}
Running 8 CPUs

Builder创建

设置手动线程数

use tokio;

fn main() {
  // 创建带有线程池的runtime
  let rt = tokio::runtime::Builder::new_multi_thread()
    .worker_threads(10)  // 10个工作线程
    .enable_io()        // 可在runtime中使用异步IO
    .enable_time()      // 可在runtime中使用异步计时器(timer)
    .build()            // 创建runtime
    .unwrap();
  std::thread::sleep(std::time::Duration::from_secs(60));
}
线程数

创建 10 个线程数 + 1 个主线程数

ps aux | grep -v grep |grep test_async| awk '{print $2}'|xargs ps -M

USER     PID   TT   %CPU STAT PRI     STIME     UTIME COMMAND
lxd670 58867 s001    0.0 S    31T   0:00.07   0:00.09 target/debug/test_async
       58867         0.0 S    31T   0:00.00   0:00.00 
       58867         0.0 S    31T   0:00.00   0:00.00 
       58867         0.0 S    31T   0:00.00   0:00.00 
       58867         0.0 S    31T   0:00.00   0:00.00 
       58867         0.0 S    31T   0:00.00   0:00.00 
       58867         0.0 S    31T   0:00.00   0:00.00 
       58867         0.0 S    31T   0:00.00   0:00.00 
       58867         0.0 S    31T   0:00.00   0:00.00 
       58867         0.0 S    31T   0:00.00   0:00.00 
       58867         0.0 S    31T   0:00.00   0:00.00 

2-2 async main

对于main函数,tokio提供了简化的异步运行时创建方式

创建#[tokio::main]多线程

通过#[tokio::main]注解(annotation),使得async main自身成为一个async runtime

默认创建多线程

use tokio;

#[tokio::main]
async fn main() { ... }

关系

// 等价于#[tokio::main]
#[tokio::main(flavor = "multi_thread"]
// 指定10 个线程数
#[tokio::main(flavor = "multi_thread", worker_threads = 10))]
#[tokio::main(worker_threads = 10))]

创建#[tokio::main]单线程

设置flavor="current_thread"

use tokio;

#[tokio::main(flavor = "current_thread")]
async fn main() { ... }

关系

use tokio;
fn main() {
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap()
        .block_on(async { ... })
}

3.多个Runtime

⚠️若你只是想控制线程数 :用 .worker_threads(n) 即可

⚠️若你并不需要 runtime 隔离 :千万别在多个线程中各建 runtime

⚠️多个 runtime 并存 :非常浪费资源且容易出问题

use std::thread;
use std::time::Duration;
use tokio::runtime::Builder;

fn main() {
    let t1 = thread::spawn(|| {
        let rt1 = Builder::new_multi_thread()
            .worker_threads(2)  // 设置 2 个线程
            .thread_name("runtime-1-worker")
            .enable_all()
            .build()
            .unwrap();

        rt1.block_on(async {
            println!("Runtime 1 start");
            tokio::time::sleep(Duration::from_secs(30)).await;
            println!("Runtime 1 done");
        });
    });

    let t2 = thread::spawn(|| {
        let rt2 = Builder::new_multi_thread()
            .worker_threads(3)  // 设置 3 个线程
            .thread_name("runtime-2-worker")
            .enable_all()
            .build()
            .unwrap();

        rt2.block_on(async {
            println!("Runtime 2 start");
            tokio::time::sleep(Duration::from_secs(30)).await;
            println!("Runtime 2 done");
        });
    });

    t1.join().unwrap();
    t2.join().unwrap();
}

查看线程数

1个主线程 + 2 个线程(t1、t2) + 5个worker_threads

ps aux | grep -v grep |grep test_async| awk '{print $2}'|xargs ps -M

USER     PID   TT   %CPU STAT PRI     STIME     UTIME COMMAND
lxd670 60647 s001    0.0 S    31T   0:00.08   0:00.10 target/debug/test_async
       60647         0.0 S    31T   0:00.00   0:00.00 
       60647         0.0 S    31T   0:00.00   0:00.00 
       60647         0.0 S    31T   0:00.00   0:00.00 
       60647         0.0 S    31T   0:00.00   0:00.00 
       60647   ·      0.0 S    31T   0:00.00   0:00.00 
       60647         0.0 S    31T   0:00.00   0:00.00 
       60647         0.0 S    31T   0:00.00   0:00.00 

4.block_on

在当前线程里,阻塞等待一个异步任务(Future)跑完,然后返回它的结果。

block_on运行async

block_on使用.await里会阻塞主线程,需要等待block_on完之后才会继续执行

use tokio::runtime::Runtime;
use chrono::Local;

fn main() {
    println!("start");
    let rt = Runtime::new().unwrap();
    rt.block_on(async {
        println!("before sleep: {}", Local::now().format("%F %T.%3f"));
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
        println!("after sleep: {}", Local::now().format("%F %T.%3f"));
    });
    println!("end");
}
start
before sleep: 2025-06-28 14:00:18.550
after sleep: 2025-06-28 14:00:28.558
end

block_on的返回值

use tokio::runtime::Runtime;
use chrono::Local;

fn main() {
    println!("start");
    let rt = Runtime::new().unwrap();
    let res = rt.block_on(async {
        println!("before sleep: {}", Local::now().format("%F %T.%3f"));
        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
        println!("after sleep: {}", Local::now().format("%F %T.%3f"));
        10
    });
    println!("end");
  	// block_on返回值
    println!("{:?}", res);
}
start
before sleep: 2025-08-05 21:33:09.362
after sleep: 2025-08-05 21:33:19.364
end
10

tokio::spawn

程序就直接退出了,after sleep根本不会打印,因为runtime drop时直接取消了任务。

如果你想“主线程退出前等所有任务完成”,要自己手动 .await 所有 JoinHandle

未设置JoinHandle

只打印了before sleep,未打印after sleep

use tokio;
use chrono::Local;

fn main() {
    println!("start");
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(2)
        .thread_name("my-worker")
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
        for i in 0..10 {
            tokio::spawn(async move {
                println!("before sleep[{}]: {}", i, Local::now().format("%F %T.%3f"));
                tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
                println!("after sleep[{}]: {}", i, Local::now().format("%F %T.%3f"));
            });
        }
    });
    println!("end");
}
start
end
before sleep[0]: 2025-06-28 13:50:58.228
before sleep[1]: 2025-06-28 13:50:58.228
before sleep[2]: 2025-06-28 13:50:58.228
before sleep[3]: 2025-06-28 13:50:58.228
before sleep[4]: 2025-06-28 13:50:58.229
before sleep[6]: 2025-06-28 13:50:58.228
before sleep[5]: 2025-06-28 13:50:58.229
before sleep[7]: 2025-06-28 13:50:58.229
before sleep[8]: 2025-06-28 13:50:58.229
before sleep[9]: 2025-06-28 13:50:58.229

设置JoinHandle

tokio::spawn会返回一个JoinHandle,使用.await等待

use tokio;
use chrono::Local;

fn main() {
    println!("start");
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(2)
        .thread_name("my-worker")
        .enable_all()
        .build()
        .unwrap();

    rt.block_on(async {
        let mut handles = Vec::new();
        for i in 0..10 {
          	// 接收tokio::spawn返回的JoinHandle
            let handle = tokio::spawn(async move {
                println!("before sleep[{}]: {}", i, Local::now().format("%F %T.%3f"));
                tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
                println!("after sleep[{}]: {}", i, Local::now().format("%F %T.%3f"));
            });
          	// 放入集合中
            handles.push(handle);
        }
        // 等待所有任务执行完成
        for h in handles {
            h.await.unwrap();
        }
    });
    println!("end");
}
start
before sleep[6]: 2025-06-28 13:59:43.507
before sleep[7]: 2025-06-28 13:59:43.507
before sleep[8]: 2025-06-28 13:59:43.507
before sleep[9]: 2025-06-28 13:59:43.507
before sleep[0]: 2025-06-28 13:59:43.507
before sleep[4]: 2025-06-28 13:59:43.507
before sleep[5]: 2025-06-28 13:59:43.507
before sleep[1]: 2025-06-28 13:59:43.507
before sleep[2]: 2025-06-28 13:59:43.507
before sleep[3]: 2025-06-28 13:59:43.507
after sleep[0]: 2025-06-28 13:59:53.508
after sleep[6]: 2025-06-28 13:59:53.509
after sleep[7]: 2025-06-28 13:59:53.509
after sleep[8]: 2025-06-28 13:59:53.509
after sleep[9]: 2025-06-28 13:59:53.509
after sleep[5]: 2025-06-28 13:59:53.509
after sleep[4]: 2025-06-28 13:59:53.509
after sleep[1]: 2025-06-28 13:59:53.509
after sleep[2]: 2025-06-28 13:59:53.509
after sleep[3]: 2025-06-28 13:59:53.508
end

阻塞对比

阻塞

block_on这直接使用.await会直接阻塞

rt.block_on(async {
    tokio::time::sleep(...).await;
});

非阻塞

tokio::spawn没有接收JoinHandle,主线程结束之后,会关闭未执行完成的异步任务

rt.block_on(async {
    tokio::spawn(async {
        tokio::time::sleep(...).await;
    });
});

阻塞

tokio::spawn接收JoinHandle 并且.await

rt.block_on(async {
    let handle = tokio::spawn(async {
        tokio::time::sleep(...).await;
    });
    handle.await.unwrap();
});
情况 会等任务完成吗?
tokio::spawn(...)后不.await ❌不会,runtime drop可能直接取消
.await JoinHandle ✅会等任务完成
block_on(async { sleep().await }) ✅会等sleep完成
block_on(async { tokio::spawn(...); }) ❌只等spawn提交完

5.Future

Rust 里的 async fn 函数调用时,只是返回一个Future,不会自动执行

✅ 想让它“跑起来”,只能:

  • .await
  • block_on
  • tokio::spawn
async fn my_async() {
    println!("Hello");
}

fn main() {
  	// 只是创建了一个Future,并不会运行
    let fut = my_async();
    println!("{}", std::any::type_name_of_val(&fut));
}
test_async::my_async::{{closure}}

6.tokio::spawn

把一个 Future 提交到 Tokio 的任务调度器,让它在后台并发执行,而不是当前 async block 顺序等待。

  • 不等它干完,先把它交给 runtime 的线程池去跑。
  • 当前任务立刻往后走。

未使用tokio::spawn

同步

use tokio;
use tokio::time::Duration;


async fn my_async1() {
    println!("my_async1 start");
    tokio::time::sleep(Duration::from_secs(2)).await;
    println!("my_async1 end");
}

async fn my_async2() {
    println!("my_async2 start");
    tokio::time::sleep(Duration::from_secs(2)).await;
    println!("my_async2 end");
}



fn main() {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
        my_async1().await;
        my_async2().await;
    });
}

使用tokio::spawn

use tokio;
use tokio::time::Duration;


async fn my_async1() {
    println!("my_async1 start");
    tokio::time::sleep(Duration::from_secs(2)).await;
    println!("my_async1 end");
}

async fn my_async2() {
    println!("my_async2 start");
    tokio::time::sleep(Duration::from_secs(2)).await;
    println!("my_async2 end");
}



fn main() {
    println!("main start");
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let a = tokio::spawn(my_async1());
        let b = tokio::spawn(my_async2());
        a.await.unwrap();
        b.await.unwrap();
    });
    println!("main end");
}
main start
my_async1 start
my_async2 start
my_async2 end
my_async1 end
main end

说明

  1. 启动Runtime

    • 内部有线程池(默认CPU核数的工作线程)
    • 有任务队列
  2. 执行block_on

    • 创建一个“根任务”,也就是这个大 async block。
    • 它在当前主线程被poll(驱动)直到完成
  3. 调用tokio::spawn(my_async1())

    • 先把 my_async1() 立即调用,得到一个 Future

      • 注意:Future只是定义,不会执行
    • 然后把这个Future包装成一个任务(Task)。

    • 把任务放到runtime的全局任务队列。

    • 后台工作线程从任务队列里取出,去poll它。

  4. 调用tokio::spawn(my_async2())

    • 立刻返回一个Future。
    • 包装成任务放进任务队列。
    • 工作线程开始调度执行。

7.Runtime::spawn()

Runtime::spawn()和`tokio::spawn()

tokio::spawn() 究竟是啥?

tokio::spawn() 本质上是一个全局函数,它内部会:

  1. 获取当前线程正在运行的Runtime Handle
  2. 调用 Handle::spawn() 把任务放到对应的Runtime
tokio::spawn(fut)
=>
Handle::current().spawn(fut)

它会依赖“当前运行时”

如果你在没有Runtime的线程里调用tokio::spawn(),程序会panic:

rt.spawn() 是什么?

rt.spawn()就是直接使用Runtime实例:

rt.spawn(fut)
// 等价于
rt.handle().spawn(fut)

既然 tokio::spawn() 很方便,为什么还要用 rt.spawn()

什么时候用 rt.spawn()

block_on外部启动任务

rt.spawn()可以正常提交任务。

❌如果你写tokio::spawn(),就会 panic:

fn main() {
    let rt = tokio::runtime::Runtime::new().unwrap();

    // 这里还没有block_on
    rt.spawn(async {
        println!("Hello from spawned task!");
    });

    // 如果你这里写 tokio::spawn(...) 会 panic
}

非Tokio线程里调度任务

这个线程里没有任何当前Runtime,只能用rt.spawn()

如果改成tokio::spawn()

use std::thread;
use tokio::runtime::Runtime;

fn main() {
    let rt = Runtime::new().unwrap();

    thread::spawn(move || {
        rt.spawn(async {
            println!("Hello from std::thread!");
        });
    }).join().unwrap();
}

多个Runtime共存

rt1.spawn()rt2.spawn()能清晰指定任务去哪一个Runtime。

如果用tokio::spawn(),它只能往当前Runtime提交,无法控制目标

fn main() {
    let rt1 = tokio::runtime::Runtime::new().unwrap();
    let rt2 = tokio::runtime::Runtime::new().unwrap();

    rt1.spawn(async {
        println!("Task in rt1");
    });

    rt2.spawn(async {
        println!("Task in rt2");
    });
}

编写库,Runtime由用户提供

这样库里不依赖“当前Runtime”,调用方可以自己决定用哪个Runtime,非常灵活。

pub fn do_something(rt: &Runtime) {
    rt.spawn(async {
        // ...
    });
}

对比

库代码用 rt.spawn(),应用代码用 tokio::spawn()

特点 tokio::spawn() rt.spawn()
是否依赖当前Runtime ✅ 依赖 ❌ 不依赖,显式指定Runtime
多Runtime共存时能指定吗 ❌ 不能 ✅ 可以指定任务跑在哪个Runtime
在非Runtime线程里能用吗 ❌ 会 panic ✅ 可以
写库时的通用性 ❌ 不推荐依赖“当前Runtime” ✅ 更灵活、健壮

8.Runtime::enter()

在其他线程使用当前的Runtime(把当前线程标记为有这个Runtime上下文)

  • rt.spawn():我明确把任务扔到Runtime,不需要当前线程有上下文。

  • tokio::spawn():要依赖当前线程已经进入某个Runtime上下文。

    • tokio::spawn()block_on() 中会
      自动查找当前Runtime,因为 block_on() 会帮你把“当前线程”注册为“有Runtime上下文”。

block_on()会自动设置当前Runtime

把这个Runtime设置为当前
在这个作用域里,所有API都能找到这个Runtime

use tokio::runtime::Runtime;

fn main() {
    let rt = Runtime::new().unwrap();

    rt.block_on(async {
        tokio::spawn(...);
    });
}

没有rt.enter()

std::thread::spawn()起的新线程里没有当前Runtime上下文

所以tokio::spawn()找不到目标,会panic报错

use tokio::runtime::Runtime;
use std::thread;

fn main() {
    let rt = Runtime::new().unwrap();

    thread::spawn({
        move || {
            // 在这个线程里没有“当前Runtime”
            // tokio::spawn() 会 panic
            tokio::spawn(async {
                println!("Hello from another thread!");
            });
        }
    }).join().unwrap();
}

rt.enter()

⚠️rt.enter() 是 Tokio 中用于手动管理运行时上下文的方法,主要适用于需要在 非异步代码(同步上下文)中启动异步任务访问 Tokio 运行时相关资源 的场景。

⚠️rt.enter() 是 Tokio 提供的 “低阶工具”,用于在同步环境中手动 “桥接” 到异步运行时,适合需要精细控制上下文的场景。

Handle是一个轻量的“指向Runtime的句柄”,你可以随便clone()

use tokio::runtime::Runtime;
use std::thread;

fn main() {
    let rt = Runtime::new().unwrap();

    // 在另一个线程里使用 Runtime
    thread::spawn({
        let rt = rt.handle().clone;
        move || {
            // 所以先enter()
            let _guard = rt.enter();

            tokio::spawn(async {
                println!("Hello from another thread!");
            // 退出_guard的上下文 = drop(guard);
            });
        }
    }).join().unwrap();
}

在线程外使用block_on

use tokio::runtime::Runtime;
use std::thread;

fn main() {
    let rt = Runtime::new().unwrap();

    // 把handle传出来 -> tokio::spawn的JoinHandle
    let handle = thread::spawn({
        let rt = rt.handle().clone();
        move || {
            let _guard = rt.enter();
						// tokio::spawn返回JoinHandle
            tokio::spawn(async {
                println!("Hello from another thread!");
            })
        }
    }).join().unwrap();
    println!("out thread!");
    // ✅ 这里等待任务完成
    rt.block_on(async {
      	// 等待的是thread::spawn中tokio::spawn的
        handle.await.unwrap();
    });
}
out thread!
Hello from another thread!

在线程内使用block_on

use tokio::runtime::Runtime;
use std::thread;

fn main() {
    let rt = Runtime::new().unwrap();

    thread::spawn({
        let rt = rt.handle().clone();
        move || {
            let _guard = rt.enter();

            rt.block_on(async {
                tokio::spawn(async {
                    println!("Hello from another thread!");
                })
            })
        }
    }).join().unwrap();
    println!("out thread!");
}

Hello from another thread!
out thread!

案例

  • await 只能在 async fnasync {} 中使用。
  • main() 是普通同步函数。
  • 所以你要么:
    • main() 变成 #[tokio::main] async fn main()
    • 要么写 rt.block_on(async { ... })
fn main() {
    let rt = tokio::runtime::Runtime::new().unwrap();

    let handle = rt.spawn(async {
        println!("start");
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        println!("end");
    });

    // ❌ 不合法:这里不能直接 await
    handle.await.unwrap();
}
fn main() {
    let rt = tokio::runtime::Runtime::new().unwrap();

    let handle = rt.spawn(async {
        println!("start");
        tokio::time::slep(std::time::Duration::from_secs(1)).await;
        println!("end");
    });

    // ✅ 把await放进block_on里的async块
    rt.block_on(async {
        handle.await.unwrap();
    });
}

比对

场景 推荐用法
同步阻塞执行一个Future block_on()
在别的线程里想spawn任务(但不执行任务) enter() + tokio::spawn()
在main里既要同步等任务完成又要spawn更多任务 block_on() + tokio::spawn()
posted @ 2026-08-10 10:03  lxd670  阅读(15)  评论(0)    收藏  举报