When beginners first hear the word “multithreading”, it often sounds scary and complicated.
But the core idea is actually very simple.
In this article, we will learn:
- What multithreading is
- Why we need it
- How workers and queues work
- A very easy Rust example
- How jobs are processed in parallel
No advanced Rust knowledge required.
What is Multithreading?
Imagine you own a pizza shop.
If only one worker cooks all pizzas:
Order 1 → Wait
Order 2 → Wait
Order 3 → Wait
Everything happens one by one.
This is similar to a single-threaded program.
Now imagine you hire 3 workers.
Worker 1
Worker 2
Worker 3
Now multiple pizzas can be prepared at the same time.
This is multithreading.
Real-Life Programming Example
Suppose your application needs to process:
- 100 PDF pages
- 500 images
- thousands of files
Doing everything one by one can be slow.
Instead, multiple workers can process jobs simultaneously.
Important Terms
Before seeing code, let’s understand some important terms.
| Term | Meaning |
|---|---|
| Thread | A separate execution path |
| Worker | A thread that performs jobs |
| Queue | A waiting line for jobs |
| Job | A task to process |
| Parallel Processing | Multiple tasks running together |
The Basic Idea
The workflow looks like this:
Main Thread
|
| sends jobs
v
+------------+
| Job Queue |
+------------+
/ | \
/ | \
W1 W2 W3
The main thread creates jobs.
Workers continuously take jobs from the queue.
Our Beginner Example
We will build:
- A job queue
- 3 worker threads
- Workers processing jobs
Full Code Example
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
// Create queue
let (sender, receiver) = mpsc::channel();
// Share receiver between workers
let receiver = Arc::new(Mutex::new(receiver));
// Create 3 worker threads
for worker_id in 1..=3 {
let receiver = Arc::clone(&receiver);
thread::spawn(move || {
loop {
// Take a job from queue
let job = {
let receiver = receiver.lock().unwrap();
receiver.recv()
};
match job {
Ok(number) => {
println!(
"Worker {} got job {}",
worker_id,
number
);
// Simulate work
thread::sleep(Duration::from_secs(2));
println!(
"Worker {} finished job {}",
worker_id,
number
);
}
Err(_) => {
println!(
"Worker {} stopping",
worker_id
);
break;
}
}
}
});
}
// Send jobs into queue
for job in 1..=10 {
println!("Main thread sending job {}", job);
sender.send(job).unwrap();
thread::sleep(Duration::from_millis(500));
}
// Close sender
drop(sender);
// Wait for workers
thread::sleep(Duration::from_secs(10));
}
Step-by-Step Explanation
1. Creating the Queue
let (sender, receiver) = mpsc::channel();
This creates a communication channel.
Think of it like a job table.
-
senderputs jobs into the table -
receivertakes jobs from the table
2. Sharing the Queue
let receiver = Arc::new(Mutex::new(receiver));
Why do we need this?
Because multiple workers will access the same queue.
Arc
Arc allows multiple threads to share ownership safely.
Mutex
Mutex ensures:
Only one worker accesses the queue at a time.
Without it, workers could conflict with each other.
3. Creating Workers
for worker_id in 1..=3
This creates:
Worker 1
Worker 2
Worker 3
Each worker runs in a separate thread.
4. Waiting for Jobs
receiver.recv()
This means:
Wait until a job arrives.
Workers continuously wait for new jobs.
5. Sending Jobs
sender.send(job).unwrap();
The main thread sends jobs into the queue.
Example:
Job 1
Job 2
Job 3
6. Workers Process Jobs
Possible output:
Main thread sending job 1
Worker 1 got job 1
Main thread sending job 2
Worker 2 got job 2
Main thread sending job 3
Worker 3 got job 3
Now all workers process tasks simultaneously.
7. Simulating Work
thread::sleep(Duration::from_secs(2));
This simulates heavy processing.
In real applications this could be:
- image processing
- AI inference
- file conversion
- database work
8. Closing the Queue
drop(sender);
This means:
No more jobs are coming.
Workers automatically stop.
Example Visualization
Imagine this queue:
[1][2][3][4][5]
Workers take jobs like this:
Worker 1 → Job 1
Worker 2 → Job 2
Worker 3 → Job 3
When a worker finishes:
Worker 1 → Job 4
Free workers immediately take the next available job.
Why Is This Faster?
Suppose each job takes:
2 seconds
If one worker processes 10 jobs:
10 × 2 = 20 seconds
With 3 workers:
Much faster because work is shared.
Real Applications of Multithreading
Multithreading is used everywhere.
Examples:
- Web servers
- AI systems
- PDF processing
- Video rendering
- Game engines
- File explorers
- Compilers
Important Beginner Note
Rust is famous for thread safety.
Many languages allow dangerous threading bugs.
Rust prevents many problems during compilation.
That is why Rust uses tools like:
ArcMutex- channels
to ensure safe concurrency.
What You Should Learn Next
After understanding this article, learn these topics next:
- Ownership in threads
-
movekeyword ArcMutexRwLock- Channels (
mpsc) - Thread pools
- Async programming with Tokio
Final Summary
Multithreading allows multiple tasks to run simultaneously.
In our example:
- Main thread creates jobs
- Jobs go into a queue
- Workers continuously take jobs
- Workers process jobs in parallel
The core architecture looks like this:
Main Thread
↓
Job Queue
↓ ↓ ↓
Workers
This simple pattern is one of the most important concepts in modern systems programming.
Top comments (0)