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?
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 ?
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.
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.
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.
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.
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.
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.
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();
}