Multithreading In C++ - C++ Programming Tutorial
C++ Course / Multithreading / Multithreading In C++

Multithreading In C++

BLUF: Mastering Multithreading In C++ is a critical step in becoming a proficient C++ developer. This lesson provides a deep dive into the syntax, performance considerations, and real-world applications of this concept.
Key Performance Insight: Multithreading In C++

C++ is renowned for its efficiency. Learn how Multithreading In C++ enables low-level control and high-performance computing in the tutorial below.

In C++, the concept of multithreading is a robust approach wherein a software application is divided into smaller units of operation referred to as threads. Multithreading empowers a CPU or a single core within a multi-core processor to execute numerous threads simultaneously. Utilizing C++ for programming permits software to assign tasks among separate threads that have the ability to function autonomously or interact with one another.

In today's computing landscape, leveraging multiple CPU cores is crucial for developing responsive, effective, and scalable applications. C++ multithreading empowers programmers to attain this by enabling simultaneous execution of code segments.

What is the Thread?

In C++, a thread is a form of operational entity utilized within a specific operation. In a multitasking setting, numerous operations can operate simultaneously. Similarly, we can run the identical operation multiple times by employing threads. Each operation in this scenario is linked with a component known as a thread.

Create a Thread

The thread class is located within the std::thread namespace. Instantiating an object of this class initiates a new thread that runs the designated callable function.

Syntax

It has the following syntax:

Example

thread name_of_thread(callable);

In this structure,

  • nameofthread: It represents an instance of the thread class.
  • Callable: It denotes a callable entity such as a function pointer or function object.
  • C++ Thread Example

Let's consider an example to demonstrate threading in C++.

Example

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:

Output

Thread is running.

Explanation

In this instance, the thread represents the class responsible for thread creation and management. Following this, the .join function guarantees that the primary thread halts its execution until the newly created thread completes its task.

Important Header Files using Thread

There are multiple header files essential for implementing multithreading in C++. Here are a few of them:

Example

#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 the realm of C++, a callable denotes any function or object capable of being executed by a thread. This category encompasses various entities like function pointers, lambda expressions, and objects that redefine their execution behavior by overloading the operator.

In C++, a callable can be declared in four different manners. These include:

1) Using the function object:

In the thread object, a function object can be employed as a callable entity. To achieve this functionality, it is necessary to define a class for the function object and then overload the operator. Upon creating the thread, the code within the overloaded function is executed.

Syntax

It has the following syntax:

Example

std::thread thread_object(functObject_class (), params)

C++ Thread Using Function Object Example

Let's consider an example to demonstrate multithreading with function objects in C++.

Example

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:

Output

Function Object Thread: Value = 10

Explanation

In this instance, we are outlining the Functor class, which makes use of overloading the operator. Subsequently, when the std::thread object constructor is called, it automatically invokes the execution of the operator method on a separate thread. The primary thread then remains in a waiting state for the secondary thread to finish by employing the t.join method.

2) Using Function Pointer

In C++, utilizing a function pointer is the most straightforward and direct approach to specifying a callable entity for a std::thread. It involves providing the memory address of a non-member function to the constructor of the thread.

Syntax

It has the following syntax:

Example

void funct_call(params)  

std::thread thread_obj(funct_call, params);    //thread using function pointer

Once the function is defined, it is necessary to instantiate the thread object with the specified function that can be called. Arguments can be supplied to the function following the function name of the object.

C++ thread using Function Pointer Example

Let's consider a scenario to demonstrate threading with a function pointer in C++.

Example

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:

Output

Function Pointer Thread: Value = 20

3) Using a Lambda Expression

In C++, a lambda function can be employed as a callable object while initializing a std::thread. Lambdas prove to be particularly handy for executing brief, inline operations without the necessity of declaring a distinct function.

Syntax

It has the following syntax:

Example

int main()  

 {  

 std::thread t1(callable_code);  

 .....  

 t1.join();  

 .....  

 }

In the provided scenario, the primary function must pause execution until thread t1 completes its task. Typically, the join method of a thread prevents concurrent operations or functionalities until the invoking thread completes its processing.

C++ thread using Lambda Expression Example

Let's consider a scenario to demonstrate the concept of threading with lambda expressions in C++.

Example

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:

Output

Lambda Thread: Value = 30

Explanation

In this instance, we generate nameless functions using lambdas, which are situated directly within the code. When utilizing the std::thread constructor, it necessitates the lambda function along with its corresponding arguments.

4) Using Member Function

In the C++ programming language, we have the ability to employ a member function of a class as a callable object for std::thread. This feature enables us to spawn threads that can work on particular instances of objects.

Syntax

It has the following syntax:

Example

std::thread thread_name(&ClassName::memberFunction, &object, args...);

C++ threads using Member Function Example

Let's consider an example to demonstrate threads using the member function in C++.

Example

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:

Output

Car is Running: 1

Car is Running: 2

Car is Running: 3

Car is Running: 4

Car is Running: 5

Explanation

In this instance, a pointer to the member function run is obtained using a &Car::run. Subsequently, the object &car is utilized to invoke the member function. The argument 6 is then supplied to the run function. Ultimately, the t.join method guarantees that the main thread pauses to allow the child thread to complete its execution.

Example of Callable Thread

We provide a comprehensive code illustration demonstrating how to create and run a thread within the showcased program.

Example

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:

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 instance, we have generated three threads employing three distinct callables: function pointer, object, and lambda expression. We instantiate two of each thread and initiate their execution. As depicted in the result, the three threads function concurrently and autonomously.

Thread Management in C++

In C++, managing threads involves overseeing and coordinating threads generated through the creation of objects from the std::thread class. Effective thread management involves supervising execution, implementing synchronization techniques, handling termination procedures, and ensuring proper resource cleanup to avoid issues like data races, memory leaks, and undefined behavior.

There are multiple functions available for controlling threads, which can be utilized for carrying out various tasks simultaneously.

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's consider an example to demonstrate the various roles of threads in C++.

Example

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:

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 instance, we illustrate the management of C++ threads by utilizing functionalities from std::thread. The code initiates the creation of four threads, with three threads being joined and one thread left to execute independently. The result displays the identification of each thread in addition to providing information about the operating system for concurrent tasks.

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 perform context switching to transition between various threads. The operating system is responsible for saving the current thread's state and loading the state of the next thread to facilitate concurrent thread operations. The overhead of context switching in multitasking scenarios can significantly affect execution speed, especially when frequent context changes occur.

C++ Context Switching in Multithreading Example

Let's consider an example to demonstrate the concept of context switching in multithreading within C++.

Example

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:

Output

Total time taken: 0.0120467 seconds

Explanation

In this instance, we determine the running time of two tasks that are executed concurrently utilizing threads. Subsequently, we utilize std::chrono to capture the beginning and ending times, create two threads to run task1 and task2 concurrently, and then synchronize them before computing and displaying the overall duration.

Passing Arguments to threads in C++

In C++ threading, we have the option to transmit multiple parameters by leveraging the s structure within a std::thread constructor. This enables us to duplicate or transfer the parameter into the internal memory of the thread entity before forwarding it to the callable function.

Syntax

It has the following syntax:

Example

std::thread thread_name(callable, arg1, arg2, ...);

C++ Passing Arguments to threads Example

Let's consider an example to demonstrate how arguments are passed to threads in C++.

Example

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:

Output

Hello! this is from thread 0

Hello! this is from thread 1

Hello! this is from thread 2

Explanation

In this instance, we initiate a new thread to run the display_alert function, displaying a message thrice. Following that, the t.join method guarantees that the primary thread pauses to allow the new thread to complete its tasks before concluding.

Problems with Multithreading

Numerous issues associated with multithreading in C++ include:

1) Race Conditions

In C++, a race condition arises when multiple threads concurrently access shared data depending on the timing of their operations. In the absence of appropriate synchronization, this can result in data corruption and inconsistent outcomes.

2) Deadlocks

In C++, a deadlock arises when numerous threads get trapped in a scenario where they are waiting for resources from other threads while also preventing each other from progressing, leading to a standstill. This situation typically happens because threads acquire locks on resources in a way that creates a circular dependency.

3) Livelocks

In the C++ programming language, livelock arises when active threads are caught in a perpetual cycle while trying to resolve resource conflicts, distinct from deadlocks.

4) Starvation

Thread starvation occurs when threads require resources for extended periods, while other threads continuously acquire and hold onto these resources. This issue can lead to a deadlock situation with lower-priority threads getting indefinitely blocked due to the priority-based scheduling mechanism.

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.

c) The execution of the program will result in an exception that will halt the running code.

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.

Option b) Delays execution until the get method is invoked.

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

To secure shared resources for ensuring thread safety.

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.

b) The main thread does not have access to its outcomes.

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

Input Required

This code uses input(). Please provide values below:

Logic Practice
Install Logic Practice
Add to home screen for a faster app-like experience