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