WHAT IS MULTI-THREADING?


Multi-threading, or multitasking, refers to managing multiple tasks simultaneously. It involves handling more than one task at a given point in time, also known as multi-processing.

It's no secret that for many jobs multitasking is an essential requirement. A common example of multitasking at work is a representative juggling numerous tasks at once like talking on the telephone, taking notes and checking emails at the same time. we all have multitasking for each minute like we talk on the phone while driving, text while keeping conversation with friends, and constantly check emails while watching favorite shows etc.

The concept of multi-threading stems from multitasking, which enables systems to execute multiple programs concurrently. There are two types of multitasking: process-based and thread-based. Process-based multitasking involves running multiple programs concurrently, while thread-based multitasking involves executing fragments of the same program simultaneously.

In a multi-threaded program, different parts can execute concurrently, with each part known as a thread. Threads enhance flexibility and efficiency, although C++ itself does not directly support multi-threading; it relies on the operating system or virtual machines.

Threads are a popular way to enhance application performance through parallelism. For example, in a browser, multiple tabs can function as different threads. MS Word utilizes multiple threads for tasks like text formatting and processing inputs. Threads outperform processes due to faster thread creation, context switching, termination, and communication.

  • Thread creation is significantly quicker.
  • Context switching between threads is more efficient.
  • Threads can be terminated with ease.
  • Communication between threads is faster.

Before creating threads in C++, it's essential to include the <thread>library. Here's an example demonstrating multi-threading in C++:

CREATING THREADS - C++ Copy to Clipboard  
#include <iostream>
#include <thread>
using namespace std;

void function() {
    cout << "Hello world: " << endl;
}

int main() {
    thread call_function(function);
    return 0;
}

In the above example, we create a thread object named call_function()with the parameter functionWhen we compile this program, a runtime error occurs because the main thread does not wait for the call_function()thread to terminate. The main thread finishes its execution while call_function()is still running, causing an error. It is essential to ensure that all threads are terminated before the main thread finishes execution.

To avoid runtime errors, it's necessary to ensure that the main thread waits for child threads to complete execution. This can be achieved by joining the threads. The join()function ensures that the main thread waits for the child thread to finish execution before continuing.

Threads are joined using the join()member function of the thread class. After joining, the main thread will wait until the child thread completes its execution.

Let's revisit the previous example and join the threads to ensure proper execution.

THREADS - C++ Copy to Clipboard  
#include <iostream>
#include <thread>
using namespace std;

void function() {
    cout << "Hello world: " << endl;
}

int main() {
    thread call_function(function);
    call_function.join();

    return 0;
}

In the above example, our program will execute successfully. However, after join()returns, the threads will no longer be joinable. Therefore, it is advisable to check if a thread is joinable using the joinable()member function of the thread class.

if(call_function.joinable()) {
   call_function.join();
}    
else 
   cout<<" Call_function is unable to join. "<< endl;
 

In our previous examples, we used functions and objects without passing any arguments. However, we can also initialize a thread using a function with parameters. Let's look at an example to see how this is done.

PASSING ARGUMENTS - C++ Copy to Clipboard  
#include <iostream>
#include <thread> //required for threads operation:
#include <chrono> //required for time operation (sleep);

using namespace std;

void functionA(int thread_number, int iterations, long delay) {
    int loop = 0;

    // Loop a specified number of iterations
    while(loop < iterations)  {

       // Sleep for a specified time 
        this_thread::sleep_for(chrono::milliseconds(delay));
        cout << "Thread " << thread_number << " Reporting: " << loop 
             << " with delay " << delay << endl;
        loop++;
    }
}

int main() {
    thread thread1(functionA, 1, 10, 1555);
    thread thread2(functionA, 2, 10, 2222);

    char result;
    cout << "Press a key to finish" << endl;
    cin >> result;

   // Join the two worker threads with the main thread
    thread1.join();
    thread2.join();

    return 0;
}

Multi-threading offers advantages such as:

  • Utilizes multiple processors for simultaneous execution, enhancing performance.
  • Offers benefits even on a uni-processor system.
  • Mitigates the impact of slow operations like accessing the hard disk by allowing other threads to continue execution.
  • Ensures efficient resource utilization by enabling other threads to proceed when one thread stalls.