Fundamentals 9 min read

Boost Rust Performance: Master Multithreading vs Futures for Concurrency

This article explores Rust's concurrent programming techniques, comparing traditional multithreading with Futures combinators, detailing thread creation, joining, returning values, the move keyword, and async execution using Tokio, and provides guidance on selecting the appropriate strategy based on task characteristics.

Architecture Development Notes
Architecture Development Notes
Architecture Development Notes
Boost Rust Performance: Master Multithreading vs Futures for Concurrency

In Rust, executing asynchronous operations concurrently can significantly improve program performance. This article examines two common concurrency strategies: multithreading and Futures combinators.

Multithread Overview

A thread is a sequence of instructions executed by the CPU, acting as a container for a running process. Multithreading allows multiple tasks to run simultaneously, improving performance but adding complexity.

Create and Manage Threads

Use std::thread::spawn to create a new thread:

<code>use std::thread;
use std::time::Duration;

fn main() {
    thread::spawn(|| {
        for i in 1..100 {
            println!("{} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}
</code>

This code creates a new thread that runs a loop printing numbers and sleeping; the main thread runs a similar loop.

Join Threads

When the main thread ends, child threads are terminated unless joined. Use a JoinHandle and its join method to wait for a thread to finish:

<code>fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("{} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("{} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }

    handle.join().expect("error joining");
}
</code>

The join call ensures the main thread does not exit before the child thread completes.

Join Position

The placement of join matters; calling it before the main loop forces the main thread to wait for the child thread before proceeding.

<code>fn main() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("{} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });
    handle.join().expect("error joining");

    for i in 1..5 {
        println!("{} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}
</code>

Retrieve Return Values with JoinHandle

Threads can return values via JoinHandle . The example creates two threads that each return a number, which are then summed:

<code>fn main() {
    let handle_1 = thread::spawn(|| {
        for i in 1..10 {
            println!("{} from the spawned thread 1!", i);
            thread::sleep(Duration::from_millis(1));
        }
        100
    });

    let handle_2 = thread::spawn(|| {
        for i in 1..10 {
            println!("{} from the spawned thread 2!", i);
            thread::sleep(Duration::from_millis(1));
        }
        200
    });

    let result_1 = handle_1.join().expect("error joining");
    let result_2 = handle_2.join().expect("error joining");

    println!("final result: {} from the main thread!", result_1 + result_2);
}
</code>

Using the move Keyword

If a closure needs ownership of external variables, prepend move :

<code>fn main() {
    let v = vec![1, 2, 3];
    let handle = thread::spawn(move || {
        println!("vector: {:?}", v);
    });
    handle.join().expect("error joining");
}
</code>

Async Operations with Tokio

Creating Async Tasks with tokio::spawn

tokio::spawn creates asynchronous tasks that may run on the current thread or a worker thread, depending on the runtime configuration (default uses the rt-multi-thread feature).

<code>use std::{thread, time::Duration};
use tokio;

#[tokio::main]
async fn main() {
    let spawn_1 = tokio::spawn(async {
        for i in 1..5 {
            println!("{} from the thread 1!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    let spawn_2 = tokio::spawn(async {
        for i in 1..5 {
            println!("{} from the thread 2!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    spawn_1.await.expect("error awaiting");
    spawn_2.await.expect("error awaiting");
}
</code>

Futures Combinators

Sequential Execution of Futures

An async function returns a Future that can be awaited. The example runs two async operations sequentially:

<code>#[tokio::main]
async fn main() {
    let start = Instant::now();

    let future_1 = async_operation(1);
    let future_2 = async_operation(2);
    future_1.await;
    future_2.await;

    println!("futures: {:?}", start.elapsed());
}

async fn async_operation(thread: i8) {
    for i in 1..5 {
        println!("{} from the operation {}!", i, thread);
        tokio::time::sleep(Duration::from_millis(400)).await;
        thread::sleep(Duration::from_millis(100));
    }
}
</code>

Concurrent Execution of Futures

Use futures::future::join_all to run multiple futures concurrently and await their completion:

<code>#[tokio::main]
async fn main() {
    let start = Instant::now();

    let future_1 = async_operation(1);
    let future_2 = async_operation(2);
    join_all([future_1, future_2]).await;

    println!("futures: {:?}", start.elapsed());
}
</code>

Summary

Multithreading vs Futures

Multithreading typically uses multiple OS threads, while Futures combinators often run on a single thread.

Multithreading suits long‑running, independent, memory‑ or CPU‑intensive tasks.

Futures combinators are better for short‑lived, I/O‑bound tasks that may not need a return value.

Choosing the Right Concurrency Strategy

Consider task type and complexity, dependencies between tasks, and available resources when selecting between multithreading and Futures.

performanceconcurrencyRusttokiomultithreadingasyncfutures
Architecture Development Notes
Written by

Architecture Development Notes

Focused on architecture design, technology trend analysis, and practical development experience sharing.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.