Multithreading in C++: Examples and Methods

Multithreading in C++: Examples and Methods

8 mins read372 Views Comment
Updated on Mar 16, 2023 17:24 IST

Multithreading is a type of software architecture that allows a single process to have multiple threads of execution. Each thread represents a separate flow of control and can run in parallel with other threads within the same process.

2023_02_Understanding-Break-Statement-in-C-6.jpg

In this article, we will be discussing multithreading in C++ in detail. In this article, we have managed to cover the following concepts in detail.

Table of Contents

  1. What is multithreading?
  2. How does multithreading work in C++?
  3. Different methods to launch a thread in C++:
    1. Function pointer
    2. Lambda expressions
    3. Function Objects
  4. Examples
  5. Advantages of Multithreading
  6. Disadvantages of multithreading

What is Multithreading?

Multithreading is useful for improving the performance and responsiveness of an application by allowing multiple tasks to run simultaneously. For example, a multithreaded application can run one thread to handle user interface events, while another thread performs background tasks such as data processing or file I/O. This can result in a more responsive and fluid user experience, as well as faster processing times for long-running tasks.

Multithreading is supported by many modern operating systems and is implemented in many programming languages, including C++. To create multithreaded applications, a programmer typically uses libraries or APIs provided by the operating system or programming language to create, manage, and synchronize threads.

Explore programming courses

How does Multithreading in C++ work?

Multithreading in C++ can be achieved using the thread library from the C++ Standard Library or by using platform-specific APIs, such as the Windows API or POSIX threads.

To create a new thread in C++, you can use the std::thread class from the <thread> library. You create a new instance of this class, passing a callable object (such as a function pointer or lambda expression) as a parameter. This callable object represents the code that will run in the new thread.

Here is an example of how you can create a new thread in C++ using the std::thread class:

 
#include
\n \n <iostream>\n \n
\n \n
#include \n \n
\n \n <thread>\n \n
\n \n
\n \n
void print_message(const char* message) {\n \n
std::cout << message << std::endl;\n \n
}\n \n
\n \n
int main() {\n \n
std::thread t(print_message, "Hello from a new thread");\n \n
t.join();\n \n
return 0;\n \n
}\n \n
\n \n </thread>\n \n
\n \n </iostream>
Copy code

In this example, the print_message function is passed as a parameter to the std::thread constructor. This creates a new thread that will run the print_message function. The join method is called on the std::thread object to wait for the new thread to complete before returning from the main.

To coordinate and synchronize the execution of multiple threads, C++ provides various synchronization mechanisms, such as mutexes, condition variables, and atomic variables. These can be used to ensure that shared data is accessed in a thread-safe manner and that the execution of multiple threads is properly ordered.

It’s worth noting that multithreading can be complex and error-prone. To write correct and efficient multithreaded code, a programmer needs to understand the underlying concepts of concurrent programming and have a good understanding of the synchronization mechanisms provided by the language and libraries used.

Explore free C++ courses

1. Launch a new thread using the function pointer

To launch a new thread in C++ using a function pointer, you can pass the function pointer as a parameter to the std::thread constructor. 

Here’s an example:

 
#include
\n \n <iostream>\n \n
\n \n
#include \n \n
\n \n <thread>\n \n
\n \n
\n \n
void print_message(const char* message) {\n \n
std::cout << message << std::endl;\n \n
}\n \n
\n \n
int main() {\n \n
void(*print_message_ptr)(const char*) = &print_message;\n \n
std::thread t(print_message_ptr, "Hello from a new thread");\n \n
t.join();\n \n
return 0;\n \n
}\n \n
\n \n </thread>\n \n
\n \n </iostream>
Copy code

In this example, the print_message_ptr variable is declared as a pointer to a function that takes a single const char* parameter and returns void. The print_message function is then assigned to this function pointer.

The std::thread constructor is then called with print_message_ptr and a string argument, which launches a new thread that will run the print_message function. The join method is called on the std::thread object to wait for the new thread to complete before returning from the main.

It is worth noting that when launching a new thread using a function pointer, you need to make sure that the function is defined and accessible at the time the thread is created. 

2. Launch a new thread using Lambda Expressions

In C++, you can launch a new thread using a lambda expression by passing it as a parameter to the std::thread constructor. 

Here’s an example:

 
#include
\n \n <iostream>\n \n
\n \n
#include \n \n
\n \n <thread>\n \n
\n \n
\n \n
int main() {\n \n
auto print_message = [](const char* message) {\n \n
std::cout << message << std::endl;\n \n
};\n \n
\n \n
std::thread t(print_message, "Hello from a new thread");\n \n
t.join();\n \n
return 0;\n \n
}\n \n
\n \n </thread>\n \n
\n \n </iostream>
Copy code

In this example, a lambda expression that takes a single const char* parameter and prints the message is defined. The lambda expression is then passed as a parameter to the std::thread constructor, which launches a new thread that will run the lambda expression.

It’s worth noting that passing a lambda expression to a thread has the advantage that it can capture variables from the surrounding scope, making it easier to pass data to the thread. However, this also means that the thread has access to those variables, so it’s important to make sure that they are properly synchronized if they are shared between the thread and other parts of the program.

3. Launch a new thread using Function Objects

In C++, you can launch a new thread using a function object (also known as a functor) by passing it as a parameter to the std::thread constructor. A function object is an object that can be called similar to a function.

Here’s an example using a function object:

 
#include
\n \n <iostream>\n \n
\n \n
#include \n \n
\n \n <thread>\n \n
\n \n
\n \n
class PrintMessage {\n \n
public:\n \n
void operator()(const char* message) const {\n \n
std::cout << message << std::endl;\n \n
}\n \n
};\n \n
\n \n
int main() {\n \n
PrintMessage print_message;\n \n
std::thread t(print_message, "Hello from a new thread");\n \n
t.join();\n \n
return 0;\n \n
}\n \n
\n \n </thread>\n \n
\n \n </iostream>
Copy code

In this example, a class PrintMessage is defined with a function call operator(). This allows an object of the class to be called similar to a function. The std::thread constructor is then called with an instance of PrintMessage and a const char* parameter, which launches a new thread that will run the function call operator of the PrintMessage object.

Function objects have the advantage that they can store state, so they can be used to pass more complex data to a thread than what can be done with a plain function or a lambda expression. However, they also have the disadvantage that they can be more difficult to understand and debug than plain functions or lambda expressions.

Examples of Multithreading in C++

Example 1: This program launches two threads that run simultaneously, each printing a message to the console: 

 
#include
\n \n <iostream>\n \n
\n \n
#include \n \n
\n \n <thread>\n \n
\n \n
\n \n
void print_message(const char* message) {\n \n
std::cout << message << std::endl;\n \n
}\n \n
\n \n
int main() {\n \n
std::thread t1(print_message, "Hello from thread 1");\n \n
std::thread t2(print_message, "Hello from thread 2");\n \n
\n \n
t1.join();\n \n
t2.join();\n \n
return 0;\n \n
}\n \n
\n \n </thread>\n \n
\n \n </iostream>
Copy code

Output:

 
Hello from thread 1
Hello from thread 2
Copy code

Example 2: In this program, two threads are launched, each running a loop that increments a shared counter. The program synchronizes access to the shared counter using a mutex to avoid race conditions:

 
#include
\n \n <iostream>\n \n
\n \n
#include \n \n
\n \n <thread>\n \n
\n \n
#include \n \n
\n \n <mutex>\n \n
\n \n
\n \n
int counter = 0;\n \n
std::mutex counter_mutex;\n \n
\n \n
void increment_counter(int num_iterations) {\n \n
for (int i = 0; i < num_iterations; ++i) {\n \n
std::unique_lock\n \n
\n \n <std::mutex>\n \n
lock(counter_mutex);\n \n
++counter;\n \n
lock.unlock();\n \n
}\n \n
}\n \n
\n \n
int main() {\n \n
std::thread t1(increment_counter, 100000);\n \n
std::thread t2(increment_counter, 100000);\n \n
\n \n
t1.join();\n \n
t2.join();\n \n
\n \n
std::cout << "Counter: " << counter << std::endl;\n \n
return 0;\n \n
}\n \n
\n \n </std::mutex>\n \n
\n \n </mutex>\n \n
\n \n </thread>\n \n
\n \n </iostream>
Copy code

Output:

 
Counter: 200000
Copy code

In this program, a global variable counter is defined along with a std::mutex named counter_mutex. A function increment_counter is also defined that takes a num_iterations parameter and increments the shared counter num_iterations times. Access to the shared counter is protected by a std::unique_lock, which acquires the lock on the mutex when it is constructed and releases the lock when the lock goes out of scope (when the unlock method is called or when the lock object is destroyed).

Two threads are then launched using the std::thread constructor, with each thread running the increment_counter function and passing a different number of iterations. Finally, the join method is called on each thread to wait for the threads to finish executing before printing the final value of the counter to the console and exiting the program.

In this example, the use of a mutex ensures that access to the shared counter is synchronized and that there are no race conditions that can lead to incorrect results.

Advantages of Using Multithreading in C++

Here are some of the advantages of using multithreading in C++:

  1. Improved Performance: Multithreading allows multiple tasks to run in parallel, making the most of multi-core processors and increasing overall program performance.
  2. Responsiveness: Multithreading can help keep a program responsive by allowing background tasks to run in separate threads, so the main thread can continue processing user inputs.
  3. Better Resource Utilization: Multithreading can help ensure that all available processing resources are utilized, preventing a single task from monopolizing the CPU and reducing overall program performance.
  4. Ease of programming: Multithreading can make programming easier by allowing complex tasks to be divided into smaller, more manageable units that can run in parallel.
  5. Better Parallelism: Multithreading can help ensure that multiple independent tasks are executed in parallel, increasing program efficiency and improving overall performance.
  6. Better Resource Sharing: Multithreading can allow multiple tasks to access shared resources, such as files, databases, and network connections, in a coordinated and synchronized manner.
  7. Simplified Code Structure: Multithreading can help simplify code structure by allowing separate tasks to be managed by separate threads, rather than having all tasks managed by a single thread.
Multithreading in Java
Multithreading in Java
The below article goes through explaining the concepts of Multithreading in Java and its implementation. Let’s begin the tutorial.
Synchronizing Threads in Java: A Practical Guide
Synchronizing Threads in Java: A Practical Guide
Synchronization in java is used to remove the data inconsistency when multiple threads access the same object or resource simultaneously. In this article, we will briefly discuss different types of...read more
Multiple Inheritance in C++ with Real-life Analogy
Multiple Inheritance in C++ with Real-life Analogy
In this article we will learn Multiple inheritance with real life analogy.This article also explains this concept with programming example with proper explanation of code.

Drawbacks of using Multithreading in C++

Here are some of the drawbacks of using multithreading in C++:

  1. Complexity: Multithreading can add complexity to a program, especially when multiple threads access shared resources, leading to race conditions and other synchronization issues.
  2. Debugging and Testing: Debugging and testing multithreaded code can be difficult, due to the complex interactions between threads and the difficulty of reproducing race conditions and other synchronization errors.
  3. Performance Overhead: Launching and managing threads can have a significant performance overhead, especially when many threads are created or destroyed frequently.
  4. Deadlocks and Livelocks: Multithreading can lead to deadlocks and livelocks, where two or more threads block each other, leading to program hangs and other synchronization errors.
  5. Memory Consistency: Maintaining memory consistency between threads can be challenging, leading to race conditions and other synchronization errors.
  6. Prioritization Issues: Determining the priority of threads and ensuring that critical tasks receive sufficient processing resources can be difficult, leading to performance issues.
  7. Portability: Multithreading code may not be portable, as threading APIs and libraries can vary across different platforms and operating systems.

Explore operating system courses

About the Author

This is a collection of insightful articles from domain experts in the fields of Cloud Computing, DevOps, AWS, Data Science, Machine Learning, AI, and Natural Language Processing. The range of topics caters to upski... Read Full Bio