Overview
Multithreading is a programming technique that allows a single process to execute multiple threads concurrently. This allows a program to perform multiple tasks simultaneously, improving the performance and responsiveness of the program. In C++, the std::thread
class, part of the C++11 standard library, is used to implement multithreading.
To use concurrency in C++, you will need to use the <thread>
header, which provides the std::thread
class and other related types and functions for creating and managing threads. Here’s a simple example of how to create and start a new thread:
#include <iostream>
#include <thread>
void foo() { std::cout << "Hello from a new thread!" << std::endl; }
int main() {
std::thread t(foo);
t.join();
return 0;
}
This program creates a new std::thread
object, passing the function foo
as the argument to the thread’s constructor. The join
member function blocks the calling thread (in this case, the main thread) until the new thread has completed execution.
You can also use the std::async
function to run a function asynchronously and get a std::future
object that can be used to retrieve the result of the function when it becomes available. For example:
#include <future>
#include <iostream>
int foo() { return 10; }
int main() {
std::future<int> f = std::async(foo);
int x = f.get();
std::cout << "x = " << x << std::endl;
return 0;
}
This program creates a new asynchronous task that executes the foo
function and returns a std::future
object that can be used to retrieve the result of the function when it becomes available. The get
member function of the std::future
object blocks the calling thread until the result is available, and then returns the result.
Basic examples
Example 1: Passing arguments to a thread function
In this example, we pass two arguments to the thread function:
#include <iostream>
#include <thread>
void foo(int x, std::string str) {
std::cout << "x = " << x << ", str = " << str << std::endl;
}
int main() {
std::thread t(foo, 10, "Hello");
t.join();
return 0;
}
When the foo
function is executed by the new thread, it will receive the arguments 10
and "Hello"
.
Example 2: Using std::move to transfer ownership of a thread
In this example, we use std::move
to transfer ownership of a thread object to a new thread:
#include <iostream>
#include <thread>
void foo() { std::cout << "Hello from a new thread!" << std::endl; }
int main() {
std::thread t1(foo);
std::thread t2 = std::move(t1);
t2.join();
return 0;
}
The t1
thread object is created and starts executing the foo
function. The t2
thread object is then created by moving the t1
object using std::move
. This transfers ownership of the thread to the t2
object, and the t1
object is left in a moved-from state and is no longer associated with any thread. The t2
object can then be used to manage the thread.
Example 3: Detaching a thread
In this example, we create a thread and detach it from the main thread of execution:
#include <iostream>
#include <thread>
void foo() { std::cout << "Hello from a new thread!" << std::endl; }
int main() {
std::thread t(foo);
t.detach();
return 0;
}
The detach
member function of the std::thread
object detaches the thread from the main thread of execution, allowing it to run independently. The main thread can then continue execution without waiting for the detached thread to complete.
Example 4: Passing arguments by reference to std::thread
In this example, we pass an argument by reference to thread function:
#include <iostream>
#include <thread>
void foo(int x, std::string &str) {
std::cout << "x = " << x << ", str = " << str << std::endl;
}
int main() {
std::string str = "Hello";
std::thread t(foo, 10, std::ref(str));
t.join();
return 0;
}
More algorithmic examples
There are many other algorithms that can be implemented as multithreaded versions to take advantage of concurrency, including the following:
-
Sorting algorithms: Many sorting algorithms, such as merge sort and quick sort, can be implemented as multithreaded versions to improve performance on multi-core systems.
-
Matrix multiplication: Matrix multiplication can be implemented as a multithreaded algorithm to take advantage of parallelism, particularly for large matrices.
-
Graph algorithms: Many graph algorithms, such as breadth-first search and depth-first search, can be implemented as multithreaded versions to improve performance on multi-core systems.
-
Search algorithms: Some search algorithms, such as binary search and linear search, can be implemented as multithreaded versions to improve performance on multi-core systems.
Example 1: Merge sort
#include <iostream>
#include <thread>
void merge(int arr[], int l, int m, int r) {
// Find sizes of two sub arrays to be merged
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;
// create temp arrays
int L[n1], R[n2];
// Copy data to temp arrays L[] and R[]
std::copy(arr + l, arr + l + n1, L);
std::copy(arr + m + 1, arr + m + 1 + n2, R);
// Merge the temp arrays back into arr[l..r]
i = 0; // Initial index of first subarray
j = 0; // Initial index of second subarray
k = l; // Initial index of merged subarray
while (i < n1 && j < n2) {
// if left subarray is smaller than right subarray then put left
// subarray element into arr and increment i and k by 1 otherwise put
// right subarray element into arr and increment j and k by 1
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
// Copy the remaining elements of L[], if there are any
std::copy(L + i, L + n1, arr + k);
// Copy the remaining elements of R[], if there are any
std::copy(R + j, R + n2, arr + k);
}
// Parallel version of the merge sort algorithm
void mergeSort(int *arr, int l, int r) {
if (l < r) {
// Same as (l+r)/2, but avoids overflow for large l and h
int m = l + (r - l) / 2;
// Sort first and second halves
std::thread t1(mergeSort, arr, l, m);
std::thread t2(mergeSort, arr, m + 1, r);
t1.join();
t2.join();
// Merge the sorted halves
merge(arr, l, m, r);
}
}
int main() {
// Test the merge sort algorithm
int arr[] = {12, 11, 13, 5, 6, 7};
int arr_size = std::size(arr);
std::cout << "Given array is \n";
for (int i = 0; i < arr_size; i++)
std::cout << arr[i] << " ";
mergeSort(arr, 0, arr_size - 1);
std::cout << "\nSorted array is \n";
for (int i = 0; i < arr_size; i++)
std::cout << arr[i] << " ";
return 0;
}