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,不会自动执行。
✅ 想让它“跑起来”,只能:
.awaitblock_ontokio::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 的任务调度器,让它在后台并发执行,而不是当前asyncblock 顺序等待。
- 不等它干完,先把它交给 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
说明
-
启动Runtime
- 内部有线程池(默认CPU核数的工作线程)
- 有任务队列
-
执行
block_on- 创建一个“根任务”,也就是这个大
asyncblock。 - 它在当前主线程被poll(驱动)直到完成。
- 创建一个“根任务”,也就是这个大
-
调用
tokio::spawn(my_async1())-
先把
my_async1()立即调用,得到一个 Future。- 注意:Future只是定义,不会执行。
-
然后把这个Future包装成一个任务(Task)。
-
把任务放到runtime的全局任务队列。
-
由后台工作线程从任务队列里取出,去poll它。
-
-
调用
tokio::spawn(my_async2())- 立刻返回一个Future。
- 包装成任务放进任务队列。
- 工作线程开始调度执行。
7.Runtime::spawn()
Runtime::spawn()和`tokio::spawn()
tokio::spawn() 究竟是啥?
tokio::spawn() 本质上是一个全局函数,它内部会:
- 获取当前线程正在运行的Runtime Handle
- 调用
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 fn或async {}中使用。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() |

浙公网安备 33010602011771号