DEV Community

Syeed Talha
Syeed Talha

Posted on

Multithreading in Rust (Beginner Guide )

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

Step-by-Step Explanation

1. Creating the Queue

let (sender, receiver) = mpsc::channel();
Enter fullscreen mode Exit fullscreen mode

This creates a communication channel.

Think of it like a job table.

  • sender puts jobs into the table
  • receiver takes jobs from the table

2. Sharing the Queue

let receiver = Arc::new(Mutex::new(receiver));
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

Without it, workers could conflict with each other.


3. Creating Workers

for worker_id in 1..=3
Enter fullscreen mode Exit fullscreen mode

This creates:

Worker 1
Worker 2
Worker 3
Enter fullscreen mode Exit fullscreen mode

Each worker runs in a separate thread.


4. Waiting for Jobs

receiver.recv()
Enter fullscreen mode Exit fullscreen mode

This means:

Wait until a job arrives.
Enter fullscreen mode Exit fullscreen mode

Workers continuously wait for new jobs.


5. Sending Jobs

sender.send(job).unwrap();
Enter fullscreen mode Exit fullscreen mode

The main thread sends jobs into the queue.

Example:

Job 1
Job 2
Job 3
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Now all workers process tasks simultaneously.


7. Simulating Work

thread::sleep(Duration::from_secs(2));
Enter fullscreen mode Exit fullscreen mode

This simulates heavy processing.

In real applications this could be:

  • image processing
  • AI inference
  • file conversion
  • database work

8. Closing the Queue

drop(sender);
Enter fullscreen mode Exit fullscreen mode

This means:

No more jobs are coming.
Enter fullscreen mode Exit fullscreen mode

Workers automatically stop.


Example Visualization

Imagine this queue:

[1][2][3][4][5]
Enter fullscreen mode Exit fullscreen mode

Workers take jobs like this:

Worker 1 → Job 1
Worker 2 → Job 2
Worker 3 → Job 3
Enter fullscreen mode Exit fullscreen mode

When a worker finishes:

Worker 1 → Job 4
Enter fullscreen mode Exit fullscreen mode

Free workers immediately take the next available job.


Why Is This Faster?

Suppose each job takes:

2 seconds
Enter fullscreen mode Exit fullscreen mode

If one worker processes 10 jobs:

10 × 2 = 20 seconds
Enter fullscreen mode Exit fullscreen mode

With 3 workers:

Much faster because work is shared.
Enter fullscreen mode Exit fullscreen mode

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:

  • Arc
  • Mutex
  • channels

to ensure safe concurrency.


What You Should Learn Next

After understanding this article, learn these topics next:

  1. Ownership in threads
  2. move keyword
  3. Arc
  4. Mutex
  5. RwLock
  6. Channels (mpsc)
  7. Thread pools
  8. 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
Enter fullscreen mode Exit fullscreen mode

This simple pattern is one of the most important concepts in modern systems programming.

Top comments (0)