Rust异步编程陷阱:Tokio的tokio::sleep和thread::sleep到底有何天壤之别?Rust的异步编程(AsyncRust)以其高性能和零成本抽象而闻名,而Tokio则是其中最受欢迎的运行时。许多初学者在尝试并发执行任务时,习惯性地在异步函数中使用标准库的
tokio::sleep 和 thread::sleep 到底有何天壤之别?Rust 的异步编程(Async Rust)以其高性能和零成本抽象而闻名,而 Tokio 则是其中最受欢迎的运行时。许多初学者在尝试并发执行任务时,习惯性地在异步函数中使用标准库的 thread::sleep 来进行延迟,却发现程序运行起来仍然是串行的。本篇文章将通过一系列实操代码示例,深入剖析这个常见的陷阱:为什么同步阻塞的 thread::sleep 会彻底破坏你的异步并发,以及如何使用 tokio::time::sleep 实现真正的非阻塞高效并发。 理解这一点,是你迈向高性能 Rust 异步开发的必经之路。
Tokio tokio::time::sleep(...) Async Rust
使用 Tokio 运行时进行并发操作
use std::{thread, time::Duration};
async fn hello(task: u64, time: u64) {
    println!("Task {task} started.");
    thread::sleep(Duration::from_millis(time));
    println!("Task {task} finished.");
}
#[tokio::main]
async fn main() {
    tokio::join!(
        hello(1, 1000),
        hello(2, 500),
        hello(3, 2000),
        hello(4, 1000),
        hello(5, 500),
        hello(6, 2000),
    );
}
use std::{thread, time::Duration};: 导入了标准库中的 thread 模块(用于线程操作)和 time::Duration(用于表示时间长度)。async fn hello(task: u64, time: u64): 定义了一个 异步函数 hello,它接受任务编号 (task) 和延迟时间(毫秒,time)作为参数。
thread::sleep(Duration::from_millis(time));: 这是代码中的一个关键点和潜在的 “陷阱”。它使用了标准库的同步线程阻塞函数来暂停执行。在 Tokio 的异步任务中,使用 thread::sleep 会阻塞 整个异步执行器线程,而不是只阻塞当前这个异步任务。在实际的异步编程中,应该使用 tokio::time::sleep 来进行非阻塞的等待。尽管如此,在这个例子中它仍然能工作,但会以阻塞的方式运行,这与异步编程的初衷相悖。#[tokio::main]: 这是一个宏,它将 main 函数标记为 Tokio 运行时的入口点,负责设置并启动异步执行器。async fn main(): 主函数是一个异步函数。tokio::join!(...): 这是一个 Tokio 宏,用于并发地执行它所包含的多个 Future (即这里的 hello(...) 调用)。它会等待所有这些异步操作全部完成后才返回。➜ cargo run
   Compiling rust_os_threads v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/rust_os_threads)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.82s
     Running `target/debug/rust_os_threads`
Task 1 started.
Task 1 finished.
Task 2 started.
Task 2 finished.
Task 3 started.
Task 3 finished.
Task 4 started.
Task 4 finished.
Task 5 started.
Task 5 finished.
Task 6 started.
Task 6 finished.
这段代码的运行结果表面上看似使用了 Tokio 的并发机制,但实际上是串行执行的。原因在于 hello 函数内部调用了同步的 thread::sleep,它会阻塞整个 Tokio 运行线程,而不是仅暂停当前异步任务。
因此,当程序运行时,每个任务都会依次启动、休眠、结束,前一个任务完全结束后,才会开始下一个任务。这就是为什么输出结果中所有任务都是按顺序执行(Task 1 → Task 2 → Task 3 …),而没有并发交错的打印信息。
换句话说,虽然程序结构上看似“异步并发”,但由于使用了同步阻塞函数,实际效果与普通的同步顺序执行没有区别。若将 thread::sleep 替换为 tokio::time::sleep,就能真正实现并发执行,每个任务会独立等待、交错输出,从而体现出 Tokio 异步运行时的并发特性。
use std::{thread, time::Duration};
async fn hello(task: u64, time: u64) {
    println!(
        "Task {task} started on thread {:?}.",
        thread::current().id()
    );
    thread::sleep(Duration::from_millis(time));
    println!("Task {task} finished.");
}
#[tokio::main]
async fn main() {
    tokio::join!(
        hello(1, 1000),
        hello(2, 500),
        hello(3, 2000),
        hello(4, 1000),
        hello(5, 500),
        hello(6, 2000),
    );
}
这段代码演示了在 Tokio 异步运行时中使用 同步阻塞操作(thread::sleep)的效果。程序定义了一个异步函数 hello,接收任务编号与延迟时间参数,在运行时打印当前任务编号及其所在线程的 ID,然后调用 thread::sleep 让当前线程暂停指定时间。由于 thread::sleep 是同步阻塞函数,它会阻塞整个线程,而非仅暂停该异步任务,从而导致所有任务被顺序执行。尽管 main 函数使用了 tokio::join! 来并发运行多个异步任务,但因为阻塞调用的存在,这些任务仍然依次在同一个线程上执行。最终输出中可看到所有任务的线程 ID 相同,说明没有真正的并发执行。如果改用 tokio::time::sleep,则能实现真正的异步并发,每个任务会在不同时间交错完成。
➜ cargo run
   Compiling rust_os_threads v0.1.0 (/Users/qiaopengjun/Code/Rust/RustJourney/rust_os_threads)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.68s
     Running `target/debug/rust_os_threads`
Task 1 started on thread ThreadId(1).
Task 1 finished.
Task 2 started on thread ThreadId(1).
Task 2 finished.
Task 3 started on thread ThreadId(1).
Task 3 finished.
Task 4 started on thread ThreadId(1).
Task 4 finished.
Task 5 started on thread ThreadId(1).
Task 5 finished.
Task 6 started on thread ThreadId(1).
Task 6 finished.
从运行结果可以看出,所有任务都在同一个线程(ThreadId(1))上依次执行,说明程序并没有真正实现并发,而是串行运行的。虽然代码使用了 Tokio 的异步运行时和 tokio::join! 宏来尝试同时执行多个异步任务,但由于 hello 函数内部调用了同步阻塞函数 thread::sleep,它会阻塞整个线程的执行,导致其他任务无法同时运行。因此,输出结果显示每个任务都是先开始、再结束,然后下一个任务才启动,所有任务共享同一个线程 ID。如果将 thread::sleep 改为 `tokio::tim...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!