Why my futures stick to a single tokio-runtime-worker

I started a lot futures and each of them are visiting rocksdb in another server with grpc;
And I know that tokio has work stealing and able to steal future which are pull-ready;
Could anyone help me with: why my futures are in the same threadid when i try print it out?
here is my code sample:

// nearly 3000 printed tracing have the same thread id
// they might be different at the very beginning of execution,
// but soon, I could have only one thread id LOL
async fn query_db(id: usize) {
    tracing::info!("[{id}] start {}", std::thread::current().id());
    // do some async query work
    // some deserd or compute work
    tracing::info!("[{id}] end {}", std::thread::current().id());
}

async fn run() -> Result<(), Box<dyn Error>> {
    let mut futs = FuturesUnordered::new();
    let mut output = vec![];

    loop {
        tokio::select! {
            // biased;
            Some(rows) = input.next() => {
                let id = rand::random::<u16>();
                tracing::info!("start futid {id}");
                futs.push(query_db(id));
            }
            Some(res) = futs.next() => {
                output.push(res?);
            }
            else => break,
        }
    }
    Ok(())
}

I could guarantee that there are a lot Ready futures; because if i wrap query_db with tokio::spawn, my total execution time is half of push future into FuturesUnordered; But I just want tokio runtime to spare futures to empty thread rather than I start a lot tokio tasks;

Because your async fn is executed on the main rather than executed as a task managed by tokio. To achieve the latter, you should warp it in tokio::spwan or other APIs. Rust Playground

Ah sorry my fault, the 'main' block is not actual main function LOL;
It is in a tokio spawn actually;

And thank you for your example;
I tried it early, yeah, when my futures amount was small(maybe less then 20), things went well, different future different thread id;
however when i have large number of futs, things goes wired

What is in tokio spawn? Your code didn't show that.

It is just a fragment of a giant repo, my bad, let me modify it to make more clear

Ah, I get your point, you mean I should spawn each future rather than just push them to the FuturesUnordered?
I got confused, because I could produce a result of different threadid even if i just push futures to FuturesUnordered;

It's always a wise talk to start with a complete minimal reproducible code, and discuss based on that.
Rust Playground

Maybe your code follows the pattern one FuturesUnordered<async fn> or two tokio::spawn(FuturesUnordered<async fn>), and you thus see the result of same ThreadID in await points when running FuturesUnordered.
But you want pattern three FuturesUnordered<Task> or four tokio::spawn(FuturesUnordered<Task>) to see different ThreadID in await points.

logs
[2023-11-15T07:47:59Z INFO  playground] [0] start ThreadId(1)
[2023-11-15T07:47:59Z INFO  playground] [1] start ThreadId(1)
[2023-11-15T07:47:59Z INFO  playground] [2] start ThreadId(1)
[2023-11-15T07:47:59Z INFO  playground] [3] start ThreadId(1)
[2023-11-15T07:47:59Z INFO  playground] [4] start ThreadId(1)
[2023-11-15T07:47:59Z INFO  playground] [5] start ThreadId(1)
[2023-11-15T07:47:59Z INFO  playground] [6] start ThreadId(1)
[2023-11-15T07:47:59Z INFO  playground] [7] start ThreadId(1)
[2023-11-15T07:47:59Z INFO  playground] [8] start ThreadId(1)
[2023-11-15T07:47:59Z INFO  playground] [9] start ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [0] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [1] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [2] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [3] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [4] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [5] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [6] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [7] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [8] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] [9] end ThreadId(1)
[2023-11-15T07:48:01Z INFO  playground] FuturesUnordered<async fn> 1606
[2023-11-15T07:48:01Z INFO  playground] [0] start ThreadId(8)
[2023-11-15T07:48:01Z INFO  playground] [1] start ThreadId(8)
[2023-11-15T07:48:01Z INFO  playground] [2] start ThreadId(8)
[2023-11-15T07:48:01Z INFO  playground] [3] start ThreadId(8)
[2023-11-15T07:48:01Z INFO  playground] [4] start ThreadId(8)
[2023-11-15T07:48:01Z INFO  playground] [5] start ThreadId(8)
[2023-11-15T07:48:01Z INFO  playground] [6] start ThreadId(8)
[2023-11-15T07:48:01Z INFO  playground] [7] start ThreadId(8)
[2023-11-15T07:48:01Z INFO  playground] [8] start ThreadId(8)
[2023-11-15T07:48:01Z INFO  playground] [9] start ThreadId(8)
[2023-11-15T07:48:02Z INFO  playground] [0] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [1] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [2] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [3] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [4] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [5] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [6] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [7] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [8] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [9] end ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] tokio::spawn(FuturesUnordered<async fn>) 1603
[2023-11-15T07:48:02Z INFO  playground] [0] start ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [1] start ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [2] start ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [3] start ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [4] start ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [5] start ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [6] start ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [7] start ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [8] start ThreadId(4)
[2023-11-15T07:48:02Z INFO  playground] [9] start ThreadId(4)
[2023-11-15T07:48:04Z INFO  playground] [1] end ThreadId(4)
[2023-11-15T07:48:04Z INFO  playground] [0] end ThreadId(8)
[2023-11-15T07:48:04Z INFO  playground] [4] end ThreadId(10)
[2023-11-15T07:48:04Z INFO  playground] [2] end ThreadId(8)
[2023-11-15T07:48:04Z INFO  playground] [3] end ThreadId(4)
[2023-11-15T07:48:04Z INFO  playground] [9] end ThreadId(11)
[2023-11-15T07:48:04Z INFO  playground] [7] end ThreadId(4)
[2023-11-15T07:48:04Z INFO  playground] [8] end ThreadId(4)
[2023-11-15T07:48:04Z INFO  playground] [5] end ThreadId(11)
[2023-11-15T07:48:04Z INFO  playground] [6] end ThreadId(10)
[2023-11-15T07:48:04Z INFO  playground] FuturesUnordered<Task> 1603
[2023-11-15T07:48:04Z INFO  playground] [9] start ThreadId(8)
[2023-11-15T07:48:04Z INFO  playground] [8] start ThreadId(8)
[2023-11-15T07:48:04Z INFO  playground] [7] start ThreadId(4)
[2023-11-15T07:48:04Z INFO  playground] [5] start ThreadId(2)
[2023-11-15T07:48:04Z INFO  playground] [6] start ThreadId(4)
[2023-11-15T07:48:04Z INFO  playground] [1] start ThreadId(11)
[2023-11-15T07:48:04Z INFO  playground] [4] start ThreadId(10)
[2023-11-15T07:48:04Z INFO  playground] [0] start ThreadId(6)
[2023-11-15T07:48:04Z INFO  playground] [3] start ThreadId(3)
[2023-11-15T07:48:04Z INFO  playground] [2] start ThreadId(5)
[2023-11-15T07:48:05Z INFO  playground] [0] end ThreadId(2)
[2023-11-15T07:48:05Z INFO  playground] [7] end ThreadId(3)
[2023-11-15T07:48:05Z INFO  playground] [9] end ThreadId(3)
[2023-11-15T07:48:05Z INFO  playground] [5] end ThreadId(3)
[2023-11-15T07:48:05Z INFO  playground] [4] end ThreadId(2)
[2023-11-15T07:48:05Z INFO  playground] [6] end ThreadId(3)
[2023-11-15T07:48:05Z INFO  playground] [8] end ThreadId(2)
[2023-11-15T07:48:05Z INFO  playground] [2] end ThreadId(3)
[2023-11-15T07:48:05Z INFO  playground] [3] end ThreadId(3)
[2023-11-15T07:48:05Z INFO  playground] [1] end ThreadId(6)
[2023-11-15T07:48:05Z INFO  playground] tokio::spawn(FuturesUnordered<Task>)  1602
2 Likes

AH, thank you sooooo much for explanation and I see what I got confused;
I do need the result in pattern 4;
But there is something I didn't mentioned(sry), when I coded like that, qps of my server is drasticly decreased when querydb is short and fast io (just index scan of a single row in DB), maybe I need to start a new question;
And thank you again for your answer!

Note

  • FuturesUnordered<Task> (pattern 3) can also execute tasks on different threads (as pattern 4 does)
    • I doubt tokio::spawn(FuturesUnordered<Task>) (pattern 4) is the recommended way, because the FuturesUnordered already takes tokio Tasks
  • tokio::spawn(FuturesUnordered<Task>) (pattern 4) sometimes acts like tokio::spawn(FuturesUnordered<async fn>) (pattern 2), in which start and end happen on mere two threads
    • so maybe you want your tasks to be executed in parallel (i.e. all tasks run on different threads)
    • but keep in mind that async tasks are not promised to be parallel, they are asynchronous instead
logs: `FuturesUnordered<Task>` vs`tokio::spawn(FuturesUnordered<Task>)`
[2023-11-16T02:04:38Z INFO  playground] [0] start ThreadId(2)
[2023-11-16T02:04:38Z INFO  playground] [1] start ThreadId(2)
[2023-11-16T02:04:38Z INFO  playground] [5] start ThreadId(2)
[2023-11-16T02:04:38Z INFO  playground] [6] start ThreadId(11)
[2023-11-16T02:04:38Z INFO  playground] [8] start ThreadId(11)
[2023-11-16T02:04:38Z INFO  playground] [9] start ThreadId(11)
[2023-11-16T02:04:38Z INFO  playground] [3] start ThreadId(9)
[2023-11-16T02:04:38Z INFO  playground] [2] start ThreadId(7)
[2023-11-16T02:04:38Z INFO  playground] [7] start ThreadId(2)
[2023-11-16T02:04:38Z INFO  playground] [4] start ThreadId(10)
[2023-11-16T02:04:40Z INFO  playground] [8] end ThreadId(8)
[2023-11-16T02:04:40Z INFO  playground] [5] end ThreadId(2)
[2023-11-16T02:04:40Z INFO  playground] [7] end ThreadId(2)
[2023-11-16T02:04:40Z INFO  playground] [4] end ThreadId(2)
[2023-11-16T02:04:40Z INFO  playground] [9] end ThreadId(2)
[2023-11-16T02:04:40Z INFO  playground] [6] end ThreadId(9)
[2023-11-16T02:04:40Z INFO  playground] [3] end ThreadId(8)
[2023-11-16T02:04:40Z INFO  playground] [2] end ThreadId(10)
[2023-11-16T02:04:40Z INFO  playground] [0] end ThreadId(11)
[2023-11-16T02:04:40Z INFO  playground] [1] end ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] FuturesUnordered<Task> 1602
[2023-11-16T02:04:40Z INFO  playground] [9] start ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] [0] start ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] [1] start ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] [2] start ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] [3] start ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] [4] start ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] [5] start ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] [6] start ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] [7] start ThreadId(7)
[2023-11-16T02:04:40Z INFO  playground] [8] start ThreadId(7)
[2023-11-16T02:04:41Z INFO  playground] [3] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] [9] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] [0] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] [1] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] [2] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] [8] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] [4] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] [5] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] [6] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] [7] end ThreadId(2)
[2023-11-16T02:04:41Z INFO  playground] tokio::spawn(FuturesUnordered<Task>)  1602
1 Like

When you use FuturesUnordered, every future is forced to be on the same thread. I recommend using a JoinSet instead.

2 Likes