使用#[tokio::main]宏创建两个运行时无法并行运行的问题
问题:Tokio两个运行时无法并行执行任务?
我正在尝试理解Tokio运行时的工作机制,特意用#[tokio::main]宏创建了两个运行时,分别执行函数a()和b()。原本以为它们会持续并行打印"im awake A"和"im awake B"(因为都调用了带循环的async_task函数),但实际程序只打印"im awake A"。既然每个运行时都有自己的线程池,为什么它们无法并行运行?
代码示例
use std::thread; fn main() { a(); b(); } #[tokio::main] async fn a() { tokio::spawn(async move { async_task("A".to_string()).await }); } pub async fn async_task(msg: String) { loop { thread::sleep(std::time::Duration::from_millis(1000)); println!("im awake {}", msg); } } #[tokio::main] async fn b() { tokio::spawn(async move { async_task("B".to_string()).await }); }
解答
核心问题1:#[tokio::main]生成的函数会阻塞当前线程直到运行时退出
#[tokio::main]宏会把你的async fn转换成同步函数,这个函数的逻辑是:创建Tokio运行时 → 在运行时执行你的异步代码 → 阻塞当前线程,直到运行时完全停止。
所以当你在main里调用a()时,主线程会被直接卡住,永远不会执行到b()——这就是你只看到"A"输出的根本原因。
核心问题2:thread::sleep会阻塞Tokio工作线程
就算你能让两个运行时都启动,thread::sleep是同步阻塞调用,会直接占满Tokio的工作线程,导致运行时没法调度其他任务。异步代码里必须用tokio::time::sleep,它是非阻塞的,等待时会让出线程给其他任务使用。
修正方案
思路1:用同一个Tokio运行时调度两个任务(推荐)
这是Tokio的标准用法,不需要创建多个运行时:
use tokio::time; #[tokio::main] async fn main() { // 同时启动两个异步任务 let task_a = tokio::spawn(async_task("A".to_string())); let task_b = tokio::spawn(async_task("B".to_string())); // 等待两个任务完成(这里是无限循环,会一直运行) tokio::join!(task_a, task_b); } pub async fn async_task(msg: String) { loop { time::sleep(time::Duration::from_millis(1000)).await; println!("im awake {}", msg); } }
思路2:强制用两个独立运行时(需放到单独线程)
如果确实需要两个独立的Tokio运行时,得把每个#[tokio::main]修饰的函数放到单独线程执行,避免阻塞主线程:
use std::thread; use tokio::time; fn main() { // 把a放到单独线程运行 thread::spawn(|| { a(); }); // 把b放到单独线程运行 thread::spawn(|| { b(); }); // 主线程不能退出,否则整个程序会终止 loop { thread::sleep(std::time::Duration::from_secs(1)); } } #[tokio::main] async fn a() { tokio::spawn(async_task("A".to_string())).await.unwrap(); } #[tokio::main] async fn b() { tokio::spawn(async_task("B".to_string())).await.unwrap(); } pub async fn async_task(msg: String) { loop { time::sleep(time::Duration::from_millis(1000)).await; println!("im awake {}", msg); } }
关键总结
#[tokio::main]生成的函数是同步阻塞的,调用后会占住当前线程直到运行时结束- 异步代码中禁止使用
thread::sleep这类同步阻塞调用,必须用Tokio提供的异步版本 - 除非有特殊需求,否则应该用单个Tokio运行时管理所有异步任务,这是Tokio的设计初衷
内容的提问来源于stack exchange,提问作者ezio
相关产品推荐
相关产品推荐

