How Programming Languages Model Asynchronous Program Flow 32 ch02/aa-os-threads use std::thread::{self, sleep}; fn main() { println!("So, we start the program here!"); let t1 = thread::spawn(move || { sleep(std::time::Duration::from_millis(200)); println!("The long running tasks finish last!"); }); let t2 = thread::spawn(move || { sleep(std::time::Duration::from_millis(100)); println!("We can chain callbacks... "); let t3 = thread::spawn(move || { sleep(std::time::Duration::from_millis(50)); println!("... like this!"); }); t3. join(). unwrap(); }); println!("The tasks run concurrently!"); t1. join(). unwrap(); t2. join(). unwrap(); } In this example, we simply spawn several OS threads and put them to sleep. Sleeping is essentially the same as yielding to the OS scheduler with a request to be re-scheduled to run after a certain time has passed. To make sure our main thread doesn't finish and exit (which will exit the process) before our children thread has had time to run we join them at the end of our main function. If we run the example, we'll see how the operations occur in a different order based on how long we yielded each thread to the scheduler: So, we start the program here! The tasks run concurrently! We can chain callbacks...... like this! The long-running tasks finish last! So, while using OS threads is great for a number of tasks, we also outlined a number of good reasons to look at alternatives by discussing their limitations and downsides. The first alternatives we'll look at are what we call fibers and green threads.