Multithreading

Parallel execution with std::thread

๐Ÿงต What is Multithreading?

Multithreading allows programs to execute multiple tasks simultaneously using separate threads, improving performance and responsiveness by utilizing multiple CPU cores effectively.


#include <thread>
#include <iostream>

void printHello() {
    std::cout << "Hello from thread!" << std::endl;
}

std::thread t(printHello); // Create and start thread
t.join(); // Wait for completion
                                    

Output:

Hello from thread!

Threading Concepts

๐Ÿš€

std::thread

Create and manage threads

std::thread t(function);
๐Ÿค

join()

Wait for thread to complete

t.join();
๐Ÿ”„

detach()

Let thread run independently

t.detach();
โšก

Parallel Execution

Multiple tasks at once

// Faster execution

๐Ÿ”น Basic Thread Example

Threads enable concurrent execution, allowing multiple tasks to run simultaneously. A std::thread object is constructed with a callable (function, lambda, functor). The thread starts execution immediately (or deferred with async). The main thread must call join() to wait for completion or detach() to let it run independently. Proper synchronization (mutexes, condition variables) is required to safely share data between threads. This concurrency model is fundamental for improving responsiveness and utilizing multi-core processors.

#include <iostream>
#include <thread>
#include <chrono>

void worker(int id) {
    for (int i = 1; i <= 3; ++i) {
        std::cout << "Thread " << id << ": Working step " << i << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    std::cout << "Thread " << id << " finished!" << std::endl;
}

int main() {
    std::cout << "Starting threads..." << std::endl;
    
    // Create two threads
    std::thread t1(worker, 1);
    std::thread t2(worker, 2);
    
    // Wait for both threads to complete
    t1.join();
    t2.join();
    
    std::cout << "All threads completed!" << std::endl;
    return 0;
}

Output:

Starting threads...
Thread 1: Working step 1
Thread 2: Working step 1
Thread 1: Working step 2
Thread 2: Working step 2
Thread 1: Working step 3
Thread 2: Working step 3
Thread 1 finished!
Thread 2 finished!
All threads completed!

๐Ÿ”น Lambda Functions with Threads

Lambda expressions provide a concise way to define thread tasks inline. Capturing local variables by value ([=]) or reference ([&]) allows the lambda to use its surrounding context. Lambdas are ideal for short, one-off tasks passed to threads, avoiding the need to define separate named functions. This pattern is common in parallel algorithms, event loops, and anywhere you need to spawn a small, focused unit of work without defining excessive boilerplate code.

#include <iostream>
#include <thread>
#include <vector>

int main() {
    std::vector<std::thread> threads;
    
    // Create multiple threads using lambda
    for (int i = 1; i <= 3; ++i) {
        threads.emplace_back([i]() {
            std::cout << "Lambda thread " << i << " is running" << std::endl;
            
            // Simulate some work
            int sum = 0;
            for (int j = 1; j <= i * 1000; ++j) {
                sum += j;
            }
            
            std::cout << "Thread " << i << " calculated sum: " << sum << std::endl;
        });
    }
    
    // Wait for all threads to complete
    for (auto& t : threads) {
        t.join();
    }
    
    std::cout << "All lambda threads finished!" << std::endl;
    return 0;
}

Output:

Lambda thread 1 is running
Lambda thread 2 is running
Lambda thread 3 is running
Thread 1 calculated sum: 500500
Thread 2 calculated sum: 2001000
Thread 3 calculated sum: 4501500
All lambda threads finished!

๐Ÿ”น Thread with Return Values

Passing data back from threads requires careful coordination to avoid races. One common method is to pass a reference or pointer to a result variable that the thread writes upon completion. The launching thread must ensure the result variable outlives the worker thread. Alternatively, std::async with std::future provides a higher-level mechanism to retrieve results. This pattern is essential for parallel computations like map-reduce, where partial results from multiple threads are combined into a final answer.

#include <iostream>
#include <thread>
#include <vector>

void calculateSquares(const std::vector<int>& input, std::vector<int>& output, int start, int end) {
    for (int i = start; i < end; ++i) {
        output[i] = input[i] * input[i];
    }
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8};
    std::vector<int> squares(numbers.size());
    
    int mid = numbers.size() / 2;
    
    // Create two threads to process different halves
    std::thread t1(calculateSquares, std::cref(numbers), std::ref(squares), 0, mid);
    std::thread t2(calculateSquares, std::cref(numbers), std::ref(squares), mid, numbers.size());
    
    // Wait for completion
    t1.join();
    t2.join();
    
    // Display results
    std::cout << "Original: ";
    for (int n : numbers) std::cout << n << " ";
    std::cout << std::endl;
    
    std::cout << "Squares:  ";
    for (int s : squares) std::cout << s << " ";
    std::cout << std::endl;
    
    return 0;
}

Output:

Original: 1 2 3 4 5 6 7 8
Squares: 1 4 9 16 25 36 49 64

๐Ÿง  Test Your Knowledge

What does join() do in threading?