Welcome to another exciting lesson at CodeYourCraft! Today, we're going to dive into the world of concurrency in Rust by exploring the std::thread module. By the end of this tutorial, you'll be able to write multi-threaded programs that can take on multiple tasks simultaneously, improving the performance of your applications.
Let's start by understanding what multithreading is. Multithreading is a programming technique that allows a single program to run multiple threads of execution concurrently. Each thread can perform different tasks or parts of a task independently, improving overall program efficiency.
In Rust, we use the std::thread module to create and manage threads. Let's start by creating a new thread.
use std::thread;
fn main() {
// Create a new thread that runs the function `my_thread_function`
let handle = thread::spawn(|| {
println!("Hello from a thread!");
});
// The main thread continues to run
println!("Hello from the main thread!");
// The new thread is also run, and we wait for it to finish
handle.join().unwrap();
}In the code above, we first import the std::thread module. Then, we define a new thread by calling thread::spawn(), which takes a closure (an anonymous function) as an argument. Inside the closure, we print a message specific to the thread.
The main thread continues to run after creating the new thread, and we print a message from the main thread. To wait for the new thread to finish, we call handle.join(), which returns the thread handle and waits for the thread to complete.
You can pass arguments to threads by creating a closure that takes arguments and using them inside the closure. Here's an example:
use std::thread;
fn main() {
let args = vec![1, 2, 3, 4, 5];
// Create a new thread for each argument
let mut handles = vec![];
for arg in args {
let arg = arg; // Capture the variable by value
let handle = thread::spawn(move || {
println!("The thread received {}", arg);
});
handles.push(handle);
}
// Wait for all threads to finish
for handle in handles {
handle.join().unwrap();
}
}In this example, we create a vector of arguments and spawn a new thread for each argument. We use the move keyword to ensure that the closure captures the variable arg by value, rather than by reference. This is essential because each thread would have its own copy of arg after the closure is moved into the new thread.
Synchronizing threads is crucial when multiple threads need to access shared resources or communicate with each other. Rust provides several synchronization primitives, such as Mutex, Condvar, and RwLock. In this lesson, we'll focus on Mutex.
use std::sync::{Mutex, Arc};
use std::thread;
fn main() {
// Create a shared counter wrapped in a Mutex
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
// Create 10 threads that increment the counter
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
// Wait for all threads to finish and print the final counter value
for handle in handles {
handle.join().unwrap();
}
println!("The final counter value is: {}", *counter.lock().unwrap());
}In this example, we create a shared counter wrapped in a Mutex to ensure safe concurrent access. We create 10 threads that increment the counter, and we use Arc::clone() to create a new Arc instance for each thread, which ensures that the Mutex is shared between threads correctly.
What does `thread::spawn()` do in Rust?
We hope you found this tutorial informative and engaging! In the next lesson, we'll dive deeper into synchronization in Rust, exploring Condvar and RwLock. Stay tuned! 🎯