Why context switch happening during cpu bound process in async threads?

Hey everyone

Quote source discussion from Tokio context switching for spawn vs spawn_blocking?:

Why the code below acting like kernel context switching? Isn't it supposed be only switching when meets .awaits ? Shouldn't be blocking since there is no IO operation ?


use std::time::{Duration, Instant};



fn is_prime(n: u32,task:&str) -> bool {
    if n == 5000|| 
      n == 10000 ||
      n == 15000 || 
      n == 25000 ||
      n == 30000 || 
      n == 35000 ||
      n == 40000 || 
      n == 45000 ||
      n == 60000 ||
      n == 70000 || 
      n == 80000 ||
      n == 90000 ||
      n == 100000 || 
      n == 110000 ||
      n == 120000 ||
      n == 130000 || 
      n == 140000 ||
      n == 145000 


    {
     println!("Current progress done FROM TASK > {} %{}",task,(n as f64 /150000 as f64)*100 as f64);

    };
    (2 ..= n/2).all(|i| n % i != 0 )
}





async fn cpu_bound_task(task:&str) {
    println!("{} Started",task);

    let now = Instant::now();
    const MAX: u32 = 150_000;
    let n_primes = (2..MAX).filter(|n| is_prime(*n,task)).count();
    println!("{} Found {} prime numbers in the range 2..{}",task,n_primes,MAX);
    let elapsed_time = now.elapsed();
    println!("{} TASK took {} seconds.",task,elapsed_time.as_millis());
}




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




            for acc in 1..=5 {

                let task_number = format!("Account {}",acc.to_string());
                let clone = task_number.clone();
                tokio::spawn
                (async move {
                    let _ = cpu_bound_task(&clone)
                    .await;

                });

                println!("{} Spawned",task_number);
            }

        }

If you use the RuntimeFlavor::MultiThread scheduler then Tokio is free to create multiple threads when you call spawn.

2 Likes

Kernel context switching refers to your operating system running multiple processes. It can switch between them whenever it wants to. This is not specific to Rust, it's just how multitasking works.

Open your system's task manager/activity monitor/htop. You'll see that there are way more processes and threads "running" than your CPU has cores. This is done by the OS constantly starting and stopping them, giving each process a few milliseconds of time to run.

2 Likes

Ah i see logical, i have two question to make sure

  1. So u saying default runtime of tokyo is Runtime Flavor::Multi Thread and if multithread exists, regardless of async it always switching (like kernel) anywhere and any time between threads at code since there is multiple threads ?

  2. What if I choose Runtime Flavor::Current_Thread? Does it only switch between threads when it meets with .await ?

Even if your async runtime, and your entire program, has only one thread, the kernel will still be context switching between your program and other programs currently running. You cannot avoid this except by using highly specialized operating systems that offer more control or are purely single-tasking.

If you use the single threaded runtime then the effect is that, within your program, you will not notice any concurrency between different parts except at await points.

6 Likes

I suspect it will help to use the word "threads" to mean execution paths managed by the operating system (like kernel) and the word "tasks" to mean execution paths managed by async Rust (like Futures). Armed with a little more precision in our terms, does this bring any clarity...

Regardless of RuntimeFlavor you are free to create any number of tasks. Each thread can be assigned any number of tasks. Each task is assigned to one thread. If RuntimeFlavor is CurrentThread then there will be one thread in your program running tasks and all tasks will be assigned to that one thread. If RuntimeFlavor is MultiThread then your program could have any number of threads with any number of tasks assigned to those threads.

For each thread, the async runtime only switches between tasks when await is met. In your original code above, if all the tasks you created were assigned to a single thread, regardless of the RuntimeFlavor, then you would have seen blocking. But, because RuntimeFlavor was MultiThread and multiple threads were created and the tasks were distributed across those threads you did not see blocking.

2 Likes

That was what i need thanks a lot :pray:

1 Like