In C++, multithreading is a powerful technique where a program is split into small units of execution known as threads. Multithreading allows a CPU or single core of a multi-core processor to run multiple threads at the same time. Programming in C++ enables applications to distribute work across autonomous threads that can operate independently or communicate with each other.
In modern computing, Using several CPU cores is essential to write responsive, efficient, and scalable applications. C++ multithreading enables developers to achieve through parallel execution of the code.
What is the Thread?
In C++, a thread is a type of working unit that is used in a particular process. In a multiprogramming environment, multiple processes can run concurrently. In the same way, we can execute the same process multiple times using threads. In this case, each process is associated with a unit called a thread.
Create a Thread
The thread class exists under the std::thread namespace. Creating an instance of this class launches a new thread that executes the specified callable task.
Syntax
It has the following syntax:
thread name_of_thread(callable);
In this syntax,
- nameofthread: It is an object of the thread class.
- Callable: It is a callable object like a function pointer or function object.
C++ Thread Example
Let us take an example to illustrate the thread in C++.
Example
#include <iostream>
#include <thread> // Required for std::thread
using namespace std; //using standard namespace
void task() {
cout << "Thread is running.\n";
}
int main() { //main function
thread t(task); // Create a thread that runs the task function
t.join(); // Wait for the thread to finish
return 0;
}
Output:
Thread is running.
Explanation
In this example, the thread is the class that is used to create and manage threads. After that, the .join method ensures that the main thread waits for the created thread to finish before proceeding.
Important Header Files using Thread
There are several header files that are required to use multithreading in C++. Some of them are as follows:
#include <thread> // For std::thread
#include <mutex> // For std::mutex, std::lock_guard
#include <future> // For std::async, std::future
#include <atomic> // For atomic operations
#include <chrono> // For time-related functions
Defining the Callable
In C++, a callable refers to any function or object that can be executed by a thread. It includes several functions, such as function pointers, lambda expressions, and objects that have the operator overloaded to define their execution behavior.
In C++, we can define a callable in four ways. These are as follows:
1) Using the function object:
In the thread object, we can use a function object as callable. If we want to implement that function object, we need a class, and inside that class, we have to overload the operator. When the thread is created, the code containing the overloaded function is executed.
Syntax
It has the following syntax:
std::thread thread_object(functObject_class (), params)
C++ Thread Using Function Object Example
Let us take an example to illustrate the thread using function object in C++.
Example
#include <iostream>
#include <thread>
using namespace std; //using standard namespace
class Functor {
public:
void operator()(int x) {
cout << "Function Object Thread: Value = " << x << endl;
}
};
int main() { //Main Function
thread t(Functor(), 10); // Pass an instance of the class and argument
t.join(); // Wait for thread to finish
return 0;
}
Output:
Function Object Thread: Value = 10
Explanation
In this example, we define the Functor class that utilizes operator overloading. After that, the object constructor for std::thread automatically triggers the operator method execution on a distinct thread. The main thread waits for the new thread to complete using the t.join method.
2) Using Function Pointer
In C++, function pointer is the simplest and most direct ways to define a callable for a std::thread. We can pass the address of a non-member function to the thread constructor.
Syntax
It has the following syntax:
void funct_call(params)
std::thread thread_obj(funct_call, params); //thread using function pointer
Once we define the function, we have to create the thread object with the callable function. We can pass the argument to the function after the function name of the object.
C++ thread using Function Pointer Example
Let us take an example to illustrate the thread using a function pointer in C++.
Example
#include <iostream>
#include <thread>
using namespace std; //using standard namespace
void functionPointerExample(int x) {
cout << "Function Pointer Thread: Value = " << x << endl;
}
int main() { //Main function
thread t(functionPointerExample, 20); // Pass function name and argument
t.join(); // Wait for thread to finish
return 0;
}
Output:
Function Pointer Thread: Value = 20
3) Using a Lambda Expression
In C++, a lambda expression can be directly used as a callable when creating a std::thread. Lambdas are mainly useful for quick, inline tasks without the need to define a separate function.
Syntax
It has the following syntax:
int main()
{
std::thread t1(callable_code);
.....
t1.join();
.....
}
In the above example, the main function will have to wait to continue until thread t1 finishes. In general, the join function of the thread blocks other actions/functionality until the thread calling finishes its execution.
C++ thread using Lambda Expression Example
Let us take an example to illustrate the thread using lambda expression in C++.
Example
#include <iostream>
#include <thread>
using namespace std; //using standard namespace
int main() { //Main Function
auto lambda = [](int x) {
cout << "Lambda Thread: Value = " << x << endl;
};
thread t(lambda, 30); // Lambda and its parameter
t.join(); // Wait for thread to finish
return 0;
}
Output:
Lambda Thread: Value = 30
Explanation
In this example, we create anonymous functions using lambdas, which exist directly inside the code. The std::thread constructor requires the lambda function together with its arguments.
4) Using Member Function
In C++, we can utilize a class member function as a callable for std∷thread. It allows us to create threads that operate on specific objects.
Syntax
It has the following syntax:
std::thread thread_name(&ClassName::memberFunction, &object, args...);
C++ threads using Member Function Example
Let us take an example to illustrate the threads using the member function in C++ .
Example
#include <iostream>
#include <thread>
using namespace std; //using standard namespace
class Car {
public:
void run(int count) {
for (int i = 1; i < count; ++i) {
cout << "Car is Running: " << i << endl;
}
}
};
int main() { //Main Function
Car car;
thread t(&Car::run, &car, 6); // Pass address of member function and object
t.join();
return 0;
}
Output:
Car is Running: 1
Car is Running: 2
Car is Running: 3
Car is Running: 4
Car is Running: 5
Explanation
In this example, we have taken a &Car::run pointer to the member function run. After that, the &car is the object on which the member function is invoked. 6 is the argument passed to the run function. Finally, the t.join function ensures the main thread waits for the child thread to finish.
Example of Callable Thread
We present a complete coding example for the creation and execution of the thread in the program shown below.
Example
#include <iostream>
#include <thread>
using namespace std; //using standard namespace
// function to be used in callable
void func_dummy(int N)
{
for (int i = 0; i < N; i++) {
cout << "Thread 1 :: callable => function pointer\n";
}
}
// A callable object
class thread_obj {
public:
void operator()(int n) {
for (int i = 0; i < n; i++)
cout << "Thread 2 :: callable => function object\n";
}
};
int main() //Main Function
{
// Define a Lambda Expression
auto f = [](int n) {
for (int i = 0; i < n; i++)
cout << "Thread 3 :: callable => lambda expression\n";
};
//launch thread using function pointer as callable
thread th1(func_dummy, 2);
// launch thread using function object as callable
thread th2(thread_obj(), 2);
//launch thread using lambda expression as callable
thread th3(f, 2);
// Wait for thread t1 to finish
th1.join();
// Wait for thread t2 to finish
th2.join();
// Wait for thread t3 to finish
th3.join();
return 0;
}
Output:
Thread 1 :: callable => function pointer
Thread 3 :: callable => lambda expression
Thread 3 :: callable => lambda expression
Thread 2 :: callable => function object
Thread 2 :: callable => function object
Thread 1 :: callable => function pointer
Explanation
In this example, we have created three threads using three different callable, i.e., function pointer, object, and lambda expression. We make two instances of each thread and start them. As shown in the output, three threads operate simultaneously and independently.
Thread Management in C++
In C++, thread management deals with controlling and coordinating threads that result from the std::thread class creation process. Thread management requires effective execution supervision alongside proper synchronization methods and termination procedures, as well as the correct cleanup of resources to prevent data races mem, leaks, and undefined behavior.
There are several functions functions that are defined to manage threads. It can be reused to perform several multiple tasks.
| Function | Description |
|---|---|
| join() | It waits for the thread to finish execution. |
| detach() | It is used to detache the thread to run independently (daemon-like). |
| joinable() | Check if the thread can be joined or detached. |
| get_id() | It returns the thread's unique identifier. |
| native_handle() | It returns the native handle for platform-specific operations. |
| swap(thread& other) | It swaps the thread handles between two thread objects. |
| hardware_concurrency() (static) | It returns the number of threads supported by the system (concurrent threads). |
| atomic | It can be used to manage shared variables between threads in a thread-safe manner without using locks. |
C++ Thread Management Example
Let us take an example to illustrate the several functions of threads in C++.
Example
#include <iostream>
#include <thread>
#include <chrono>
using namespace std; //using standard namespace
void task(int n) {
cout << "Thread " << n << " started. ID: " << this_thread::get_id() << endl;
this_thread::sleep_for(chrono::milliseconds(300));
cout << "Thread " << n << " finished.\n";
}
int main() { //Main Function
cout << "System supports " << thread::hardware_concurrency() << " concurrent threads.\n\n";
thread t1(task, 1);
thread t2(task, 2);
thread t3(task, 3);
thread t4(task, 4); // Detached
cout << "Thread IDs:\n";
cout << "t1: " << t1.get_id() << "\n";
cout << "t2: " << t2.get_id() << "\n";
cout << "t3: " << t3.get_id() << "\n";
cout << "t4: " << t4.get_id() << "\n\n";
if (t1.joinable()) t1.join();
if (t2.joinable()) t2.join();
if (t3.joinable()) t3.join();
if (t4.joinable()) t4.detach();
cout << "\nMain thread ends after managing all threads.\n";
this_thread::sleep_for(chrono::seconds(1)); // Let detached t4 finish
return 0;
}
Output:
System supports 4 concurrent threads.
Thread 2 started. ID: 131265380484800
Thread 3 started. ID: 131265369999040
Thread 1 started. ID: 131265390970560
Thread IDs:
t1: 131265390970560
t2: 131265380484800
t3: 131265369999040
t4: 131265359513280
Thread 4 started. ID: 131265359513280
Thread 3 finished.
Thread 4 finished.
Thread Thread 2 finished.
1 finished.
Main thread ends after managing all threads.
Explanation
In this example, we demonstrate the C++ thread management by employing std::thread features. The program creates four threads while it joins three other threads and allows one thread to run by itself. The output shows both thread identifiers along with the operating system details for concurrent operations.
Benefits of Multithreading
Several benefits of the multithreading in C++ are as follows:
- It helps to enhance performance on multi-core systems.
- It offers better responsiveness in GUI applications.
- It has an efficient resource utilization.
- It has parallel execution of independent tasks.
Context Switching in Multithreading
CPU executions carry out context switching between different threads. The operating system needs to create a backup of the current thread state and retrieve the state of the next thread to enable multiple threads to operate. The necessity of context switching for multitasking operations produces performance overhead that particularly impacts execution speed when context changes occur frequently.
C++ Context Switching in Multithreading Example
Let us take an example to illustrate the context switching in multithreading in C++.
Example
#include <iostream>
#include <thread>
#include <chrono>
using namespace std; //using standard namespace
void task1() {
for (int i = 0; i < 1000000; ++i) {
// Simulate work
}
}
void task2() {
for (int i = 0; i < 1000000; ++i) {
// Simulate work
}
}
int main() { //Main Function
auto start = std::chrono::high_resolution_clock::now();
std::thread t1(task1);
std::thread t2(task2);
t1.join();
t2.join();
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << "Total time taken: " << duration.count() << " seconds\n";
return 0;
}
Output:
Total time taken: 0.0120467 seconds
Explanation
In this example, we find the execution time of two tasks that are running concurrently using threads. After that, it uses std::chrono to record the start and end times, makes two threads to execute task1 and task2 simultaneously, and then joins them before calculating and printing the total time taken.
Passing Arguments to threads in C++
In C++ threading, we can pass multiple arguments using the s structure in a std∷thread constructor. It allows us to copy or move the argument into the internal storage of the thread object and then pass it to the callable.
Syntax
It has the following syntax:
std::thread thread_name(callable, arg1, arg2, ...);
C++ Passing Arguments to threads Example
Let us take an example to illustrate the passing arguments to threads in C++.
Example
#include <iostream>
#include <thread>
using namespace std; //using standard namespace
void show_msg(string msg, int count) {
for (int i = 0; i < count; ++i) {
cout << msg << " " << i << endl;
}
}
int main() { //main function
thread t(show_msg, "Hello! this is from thread", 3); //It prints the output
t.join();
return 0;
}
Output:
Hello! this is from thread 0
Hello! this is from thread 1
Hello! this is from thread 2
Explanation
In this example, we create a different thread to execute the show_message function that shows a message 3 times. After that, the t.join call ensures the main thread waits for the created thread to finish execution before exiting.
Problems with Multithreading
Several problems with multithreading in C++ are as follows:
1) Race Conditions
In C++, a race condition occurs when multiple threads access common data concurrently depending on exactly when they run their operations. Without proper synchronization, it can lead to data corruption and inconsistent results.
2) Deadlocks
In C++, a deadlock occus when multiple threads remaining stuck in a state where they require resources from other threads while blocking each other, resulting in a situation where none of them can continue. A deadlock occurs during application execution because threads acquire resource locks that form a circular pattern.
3) Livelocks
In C++, livelock occurs between deadlocks because active threads stay stuck in an endless loop while attempting to break their resource conflicts.
4) Starvation
Thread starvation happens because threads need resources for extended periods, yet other threads keep taking hold of these resources. The priority-based scheduling approach can create a deadlock where lower-priority threads remain permanently blocked.
C++ Multithreading MCQs
1) What happens if std::thread is created but not joined or detached before it is destroyed in C++?
- It is automatically joined
- The program continues normally
- The program execution will produce an exception that terminates the running code.
- The thread automatically detaches upon destruction.
2) Which of the following statements is accurate about the std::async function in C++?
- The std::thread functionality produces an independent thread when executed.
- It defers execution until get is called.
- It returns void.
- It blocks the calling thread until completion.
3) What is the main purpose of std::mutex in C++ multithreading?
- To pause thread execution
- To create new threads
- To lock shared resources for thread safety
- To terminate threads
4) Which one of the following options is the main drawback of using detached threads in C++?
- Joined threads execute at a faster speed than detached threads do.
- The parent thread maintains no access to their results.
- They consume more memory
- All detached threads need static declaration.
5) Which of the following is NOT a valid way to create a thread in C++ using std::thread?
- Passing a function pointer
- Passing a lambda function
- Passing a functor (function object)
- Calling run on a thread object