Where/How/When Context Switching happen between tasks in thread/threads (std::thread)?

Hey everyone i have some knowledge missing about threads .
Lets create an environment: Single CPU, Single core. Using Single Process and Multi-threads in it.

I know in tokio context switch happens only when code meets on .await. But didint understand how/where/when this happens in std::thread.

There is classic example in rust doc but i dont find this example well.

use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().unwrap();
}

The calls to thread::sleep force a thread to stop its execution for a short duration, allowing a different thread to run

Using little millisecond sleep to ensure move to another thread seems not healthy.

Q1 : Lets say there is cpu bound tasks in both thread (main and spawned one). How would switch happens between threads in this case ? Would one of threads block other one till its own cpu bound task done?

Q2: The second is an example that is often the case when having a UI. How do you prevent the whole UI from becoming unresponsive while performing other CPU intensive tasks?

You don't need to sleep to ensure that all threads get to run. The scheduler of the operating system makes sure that everyone gets their turn.

You don't do CPU-intensive tasks on the thread managing the UI.

2 Likes

So if there is cpu bound tasks for both threads, OS will schedule so that switching will be at anywhere in code and extremely fast between threads without blocking, right ?

1 Like

This example isn't there to ensure the ordering, it's just a demonstration of interleaving. In fact the book says:

The output from this program might be a little different every time

Relatively fast. Typically on linux it lets each thread run for 0.1 seconds before switching to a different one.

3 Likes

Actually u can change the example and think like a basic game, u will need a lot of task to handle and need to switch between tasks to not make the game unresponsive or broken.

1 Like

Games don't usually have some unbounded computation running in parallel in a background thread. They run all the computations they need to perform in order to display the next frame, and then display it.

But also there are ways to tell the operating system which thread has higher priority, in case you need to preempt the background thread more quickly than the default.

6 Likes

Technically yes, your program can be unresponsive for about hundreds of microseconds for every 1 milliseconds which can be critical for things like airplane engine control programs. But humans can't recognize it.

2 Likes

I'm not sure how you define "extremely" fast, but yes, generally it will switch at unspecified points in the code, and frequently enough to ensure reasonable response times.

You may want to read more about the difference between cooperative and preemptive multitasking. Most OSes force pre-emptive multitasking the context of native threads, whereas Rust's async mechanism is cooperative.

5 Likes

This would be rather unusual these days -- multicore CPUs are almost everywhere. The answer is still roughly the same, that the preemptive operating system decides when and where to run each thread, among all running processes.

Also, note that cooperative runtimes like tokio are not exempt from this. They only manage their own set of tasks, but the OS will also preempt that code for other threads if needed.

2 Likes

Ah so avarage (based tests) if i spawn hundreds threads each thread will run about 0.1 seconds and then switches to another one, that goes on like this. (i am just taking it theoretically, of course it depend how os schedule the switching within priority and other factors )

By the way forgot to add one missing environment condition im talking for multi-threads in single process. But i assume answers will be same, just wanted to note here the additional.

Note that this is only about yielding control to the tokio runtime. Any code your program runs is still running on top of a thread and the OS can preempt it whenever it wants.

1 Like

Here is the better example of preemptive multithreading than rust docs.(Context switching between threads)

Thanks a lot for everyone.

fn cpu_bound_task(task:&str) {
    const MAX: u32 = 50_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);
}




 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 == 49000
    {
     println!("Current progress done FROM TASK > {} %{}",task,(n as f64 /50000 as f64)*100 as f64);

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



    fn main() {
    let task = "A";
    let task2 = "B";
    let task3 = "C";

    let t1 = std::thread::spawn(move || {
        cpu_bound_task(task)
    });

    let t2 = std::thread::spawn(move || {
        cpu_bound_task(task2)
    });

    cpu_bound_task(task3);
    t1.join();
    t2.join();
    }

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.