Exploring the Basics of Rust’s Thread Concept
A thread is the smallest executable unit of a process. Threads are lightweight units of execution that allow a program to perform multiple tasks concurrently. In this article, we will explore the Rust thread concept.
Creating a New Thread:
To create a new thread in Rust, we can create a new thread by calling the thread::spawn() function . This function takes a closure (anonymous function) as input, that contains the code we want to run in the new thread.
Here is an example:
use std::thread;
use std::time::Duration;
fn main() {
// create a thread
thread::spawn( || {
// everything in here runs in a separate thread
for i in 0..10 {
println!("Hi, number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
// main thread
for i in 0..5 {
println!("Hi, number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
It’s important to note that, If the main thread finishes execution in a Rust program, all created spawned threads are automatically stopped, even if those spawned threads haven’t completed their tasks.
Using join() to Manage Thread Lifecycle:
To ensure a spawned thread completes its execution, store the result of thread::spawn in a variable(JoinHandle) and call its join method. This will make the main thread wait for the spawned thread to finish.
use std::thread;
use std::time::Duration;
fn main() {
// create a thread and save the handle to a variable
let handle = thread::spawn( || {
// everything in here runs in a separate thread
for i in 0..10 {
println!("Hi, number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
// main thread
for i in 0..5 {
println!("Hi, number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
// wait for the separate thread to complete
handle.join().unwrap();
}
Transferring Ownership with move Closures in Threads:
When we create a new thread, we can pass a closure (anonymous function) to it. Sometimes, the closure needs to use values from the outside of its own scope. To make sure those values get transferred to the new thread, we use the move keyword. This tells Rust to move the ownership of those values into the closure, so the new thread can use them.
use std::thread;
fn main() {
// Create a vector of integers
let values = vec![1, 2, 3];
// Spawn a new thread
let thread_handle = thread::spawn(move || {
println!("The vector passed to this thread is: {:?}", values);
});
// Wait for the spawned thread to finish executing
thread_handle.join().unwrap();
}
Communicating Between Threads with Message Passing:
In Rust, message passing is a technique used for transferring data between threads.
The Rust standard library provides an implementation of channels, which are a programming concept used for message passing. A channel is like a one-way pipe where one thread can send data and another thread can receive it. The channel has two parts: a transmitter and a receiver. One thread calls methods on the transmitter to send data, and another thread checks the receiver to get the incoming messages.
use std::sync::mpsc;
use std::thread;
fn main() {
// Create a channel
let (sender, receiver) = mpsc::channel();
// Create a new thread to send a message
let sender_thread = thread::spawn(move || {
// Prepare a message to send
let message = String::from("Hello from the sender thread!");
// Send the message through the channel
sender.send(message).unwrap();
});
// Receive the message in the main thread
let received_message = receiver.recv().unwrap();
println!("The main thread received: {}", received_message);
}
The mpsc in mpsc::channel() stands for “multiple producers, single consumer”. This means the channel can have multiple threads sending messages into it, but only one thread can receive those messages.
Example of sending multiple messages from the multiple producers:
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// Create a channel
let (sender, receiver) = mpsc::channel();
// Create the first thread to send messages
let first_sender = sender.clone();
let first_thread = thread::spawn(move || {
let messages = vec![
String::from("Hello"),
String::from("from"),
String::from("the"),
String::from("first"),
String::from("thread!"),
];
for message in messages {
first_sender.send(message).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
// Create the second thread to send messages
let second_thread = thread::spawn(move || {
let messages = vec![
String::from("Additional"),
String::from("messages"),
String::from("from"),
String::from("the"),
String::from("second"),
String::from("thread."),
];
for message in messages {
sender.send(message).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
// Receive and print the messages in the main thread
for received in receiver {
println!("Received: {}", received);
}
}