Thread basics

As we said, every program starts with a main thread. To create an independent execution point from anywhere in the program, the main thread can spawn a new thread, which becomes its child thread. Child threads can further spawn their own threads. Let's look at a concurrent program in Rust that uses threads in the simplest way possible:

// thread_basics.rs

use std::thread;

fn main() {
thread::spawn(|| {
println!("Thread!");
"Much concurrent, such wow!".to_string()
});
print!("Hello ");
}

In main, we call the spawn function from the thread module which takes a no parameter closure as an argument. Within this closure, we can write any code that we want to execute concurrently as a separate thread. In our closure, we simply print some text and return String. Compiling and running this program gives us the following output:

$ rustc thread_basics.rs
$ ./thread_basics

Hello

Strange! We only get to see "Hello" being printed. What happened to println!("Thread"); from the child thread ? A call to spawn creates the thread and returns immediately and the thread starts executing concurrently without blocking the instructions after it. The child thread is created in the detached state. Before the child thread has any chance to run its code, the program reaches the print!("Hello"); statement and exits the program when it returns from main. As a result, code within the child thread doesn't execute at all. To allow the child thread to execute its code, we need to wait on the child thread. To do that, we need to first assign the value returned by spawn to a variable:

let child = thread::spawn(|| {
print!("Thread!");
String::from("Much concurrent, such wow!")
});

The spawn function returns a JoinHandle type, which we store in the child variable. This type is a handle to the child thread, which can be used to join a thread—in other words, wait for its termination. If we ignore the JoinHandle type of a thread, there is no way to wait for the thread. Continuing with our code, we call the join method on the child before exiting from main as in the following:

let value = child.join().expect("Failed joining child thread");

Calling join blocks the current thread and waits for the child thread to finish before executing any line of code following the join call. It returns a Result value. Since we know that this thread does not panic, we call expect to unwrap the Result type giving us the string. Joining the thread can fail if a thread is joining itself or gets deadlocked, and, in that case, it returns an Err variant with the value that was passed to the panic! call though, in this case, the returned value is of the Any type which must be downcasted to a proper type. Our updated code is as follows:

// thread_basics_join.rs

use std::thread;

fn main() {
let child = thread::spawn(|| {
println!("Thread!");
String::from("Much concurrent, such wow!")
});

print!("Hello ");
let value = child.join().expect("Failed joining child thread");
println!("{}", value);
}

Here's the output of the program:

$ ./thread_basics_join
Hello Thread!
Much concurrent, such wow!

Great ! We wrote our first concurrent hello world program. Let's explore other APIs from the thread module.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset