...

/

Puzzle 19: Explanation

Puzzle 19: Explanation

Let’s learn how threads and delays work in Rust.

Test it out

Hit “Run” to see the code’s output.

[package]
name = "sleepless"
version = "0.1.0"
edition = "2018"

[dependencies]
tokio = { version = "1.7", features = ["full"] }
Running multiple tasks concurrently in Rust

Explanation

The outcome is surprising because the join macro promises to run the three instances of count_and_wait() concurrently. But, the output shows that the tasks are running sequentially, which tends to surprise newcomers to Rust’s async system. Understanding the differences between asynchronous and thread programming can help us avoid pitfalls and pick the right model for our program.

Asynchronous programs and multi-threaded programs operate differently, and each has its own strengths and weaknesses. Asynchronous (future-based) tasks are not the same as threaded tasks. They require some care to ensure that they operate concurrently. However, it’s entirely possible to run an asynchronous program on one thread. The following diagram shows the basic differences between threaded and asynchronous execution:

In a threaded model, each task operates inside a full operating system-supported thread. Threads are scheduled independently of other threads and processes. An asynchronous model stores tasks in a task queue and runs them until they yield control back to the executing program.

Let’s examine a few approaches to running this teaser concurrently.

Native threads

Threads are pre-emptively scheduled by your operating system. While the thread is suspended, other threads continue to run. A purely threaded version of this teaser looks like this:

Press + to interact
use std::thread;
use std::time::Duration;
fn count_and_wait(n: u64) -> u64 {
println!("Starting {}", n);
std::thread::sleep(Duration::from_millis(n * 100));
println!("Returning {}", n);
n
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let a = thread::spawn(|| count_and_wait(1));
let b = thread::spawn(|| count_and_wait(2));
let c = thread::spawn(|| count_and_wait(3));
a.join().unwrap();
b.join().unwrap();
c.join().unwrap();
Ok(())
}

The program spawns three threads, and they each run concurrently. Because the program calls sleep and delays execution on each ...