Multithreading in C++ (original) (raw)

Multithreading is a programming technique in which a program is divided into multiple threads that can run concurrently. It helps improve performance and responsiveness by allowing multiple tasks to execute simultaneously while sharing the same memory space.

**Note: In C++, multithreading support was introduced in C++11 through the header file.

Create a Thread

The std::thread class represents a thread. Creating an instance of this class starts a thread with the given callable as its task.

thread thread_name(callable);

where,

#include <bits/stdc++.h> using namespace std;

// Function to be run by the thread void func() { cout << "Hello from the thread!" << endl; }

int main() {

// Create a thread that runs 
// the function func
thread t(func);

// Main thread waits for 't' to finish
t.join();  
cout << "Main thread finished.";
return 0;

}

`

Output

Hello from the thread! Main thread finished.

**Explanation: In the above program we have created a thread t that prints "Hello from the thread!" and this thread is joined with the main thread so that the main thread waits for the completion of this thread and once the thread t is finished the main thread resumes its execution and prints " Main thread finished".

Joining a Thread

In C++, the join() function is used to wait for a thread to finish execution. Before calling join(), it is recommended to check whether the thread is joinable using the joinable() method.

Checking if a Thread is Joinable

The joinable() function checks whether a thread object is associated with an active thread.

thread_name.joinable();

Using join() Function

The join() function is used to wait for a thread to finish execution.

thread_name.join();

**Note: Improper synchronization between multiple threads may lead to issues such as race conditions or logic errors.

Detaching a thread

A joined thread can be detached from the calling thread using the detach() member function of the std::thread class. When a thread is detached, it runs independently in the background, and the other thread does not wait for it to finish.

thread_name.detach();

Getting Thread ID

In Multithreading in C++ each thread has a unique ID which can be obtained by using the get_id() function.

thread_name.get_id();

The get_id() function returns an object representing the thread’s ID

**Example: Program using the above operations altogether.

C++ `

#include #include #include using namespace std;

void task1() { cout << "Thread 1 is running. ID: " << this_thread::get_id() << "\n"; }

void task2() { cout << "Thread 2 is running. ID: " << this_thread::get_id() << "\n"; }

int main() { thread t1(task1); thread t2(task2);

// Get thread IDs
cout << "t1 ID: " << t1.get_id() << "\n";
cout << "t2 ID: " << t2.get_id() << "\n";

// Join t1 if joinable
if (t1.joinable()) {
    t1.join();
    cout << "t1 joined\n";
}

// Detach t2 if joinable
if (t2.joinable()) {
    t2.detach();
    cout << "t2 detached\n";
}

// Give detached thread time to complete
this_thread::sleep_for(chrono::milliseconds(100));

cout << "Main thread finished.\n";
return 0;

}

`

Output

Thread 1 is running. ID: Thread 2 is running. ID: 140737213290176 140737347512000 t1 ID: 140737347512000 t2 ID: 140737213290176 t1 joined t2 detached Main thread finished.

Callables in Multithreading

A callable object such as a function, lambda expression, or function object can be passed to a thread in C++. The thread starts executing the callable in parallel as soon as it is created.

In C++, callable can be divided into 4 categories:

Function Pointer

A function can be a callable object to pass to the thread constructor for initializing a thread.

C++ `

#include <bits/stdc++.h> using namespace std;

// Function to be run // by the thread void func(int n) { cout << n; }

int main() {

// Create a thread that runs 
// the function func
thread t(func, 4);

// Wait for thread to finish
t.join();
return 0;

}

`

**Lambda Expression

Thread object can also use a **lambda expression as a callable. Which can be passed directly inside the thread object.

C++ `

#include #include

using namespace std;

int main() { int n = 3;

// Create a thread that runs 
// a lambda expression
thread t([](int n){
    cout << n;
}, n);

// Wait for the thread to complete
t.join();
return 0;

}

`

**Output

3

**Function Objects

Function Objects or **Functors can also be used for a thread as callable. To make functors callable, we need to overload the operator parentheses operator ****()**.

C++ `

#include #include using namespace std;

// Define a function object (functor) class SumFunctor { public: int n; SumFunctor(int a) : n(a) {}

// Overload the operator() to 
// make it callable
void operator()() const {
    cout << n;
}

};

int main() {

// Create a thread using 
// the functor object
thread t(SumFunctor(3));

// Wait for the thread to 
// complete
t.join();
return 0;

}

`

**Output

3

**Non-Static and Static Member Function

We can also use thread using the non-static or static member functions of a class. For non-static member function, we need to create an object of a class but it's not necessary with static member functions.

C++ `

#include #include

using namespace std;

class MyClass { public: // Non-static member function void f1(int num) { cout << num << endl; }

// Static member function that takes one parameter
static void f2(int num) {
    cout << num;
}

};

int main() {

// Member functions 
// requires an object
MyClass obj;

// Passing object and parameter
thread t1(&MyClass::f1, &obj, 3);

t1.join(); 

// Static member function can 
// be called without an object
thread t2(&MyClass::f2, 7);

// Wait for the thread to finish
t2.join();  

return 0;

}

`

**Output

3 7

Thread Management

In C++ thread library, various functions are defined to manage threads that can be reused to perform multiple tasks. Some of the are listed below:

Classes/Methods Description
**join() It ensures that the calling thread waits for the specified thread to complete its execution.
**detach() Allows the thread to run independently of the main thread, meaning the main thread does not need to wait.
**mutex A mutex is used to protect shared data between threads to prevent data races and ensure synchronization.
**lock_guard A wrapper for mutexes that automatically locks and unlocks the mutex in a scoped block.
**condition_variable Used to synchronize threads, allowing one thread to wait for a condition before proceeding.
**atomic Manages shared variables between threads in a thread-safe manner without using locks.
**sleep_for() Pauses the execution of the current thread for a specified duration.
**sleep_until() Pauses the execution of the current thread until a specified time point is reached.
**hardware_concurrency() Returns the number of hardware threads available for use, allowing you to optimize the use of system resources.
**get_id Retrieves the unique ID of the current thread, useful for logging or debugging purposes.

Problems with Multithreading

Multithreading improves the performance and utilization of CPU, but it also introduces various problems:

Thread Synchronization

Thread synchronization is the process of controlling multiple threads when accessing shared resources to avoid data inconsistency and race conditions. It ensures that only one thread accesses a critical resource at a time.

Context switch in multithreading

A context switch occurs when the CPU stops executing one thread and starts executing another thread. The state of the current thread is saved so that execution can continue later from the same point.