Multithreading is a powerful feature in C++ that allows a program to perform multiple tasks concurrently. This can significantly improve the performance of applications, especially those that are CPU-bound or involve I/O operations. In this section, we will cover the basics of multithreading, including creating and managing threads, synchronization, and common pitfalls.

Key Concepts

  1. Thread: A thread is the smallest unit of a process that can be scheduled for execution. It is a sequence of programmed instructions that the operating system can manage independently.
  2. Concurrency: The ability of a program to execute multiple tasks simultaneously.
  3. Parallelism: The simultaneous execution of multiple tasks on multiple processors or cores.
  4. Synchronization: Mechanisms to control the access of multiple threads to shared resources to prevent data races and ensure data consistency.

Creating and Managing Threads

In C++, the <thread> library provides the necessary tools to create and manage threads. Here is a basic example of creating a thread:

#include <iostream>
#include <thread>

// Function to be executed by the thread
void printMessage() {
    std::cout << "Hello from the thread!" << std::endl;
}

int main() {
    // Create a thread that runs the printMessage function
    std::thread t(printMessage);

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

    std::cout << "Hello from the main thread!" << std::endl;

    return 0;
}

Explanation

  • #include <thread>: Includes the thread library.
  • void printMessage(): A function that will be executed by the thread.
  • std::thread t(printMessage): Creates a new thread t that runs the printMessage function.
  • t.join(): Waits for the thread t to finish its execution before continuing with the main thread.

Synchronization

When multiple threads access shared resources, synchronization is necessary to prevent data races and ensure data consistency. The <mutex> library provides mechanisms for synchronization.

Mutex Example

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // Mutex for critical section

void printMessage(const std::string& message) {
    std::lock_guard<std::mutex> lock(mtx); // Lock the mutex
    std::cout << message << std::endl;
}

int main() {
    std::thread t1(printMessage, "Hello from thread 1");
    std::thread t2(printMessage, "Hello from thread 2");

    t1.join();
    t2.join();

    return 0;
}

Explanation

  • std::mutex mtx: Declares a mutex mtx.
  • std::lock_guard<std::mutex> lock(mtx): Locks the mutex mtx for the duration of the lock object’s lifetime.
  • t1.join() and t2.join(): Wait for both threads to finish.

Common Pitfalls

  1. Deadlock: Occurs when two or more threads are waiting for each other to release resources, causing them to be stuck indefinitely.
  2. Race Condition: Occurs when the outcome of a program depends on the sequence or timing of uncontrollable events such as thread scheduling.
  3. Starvation: Occurs when a thread is perpetually denied access to resources it needs for execution.

Practical Exercise

Exercise: Sum of Array Elements Using Multiple Threads

Write a program that calculates the sum of an array's elements using multiple threads.

Solution

#include <iostream>
#include <thread>
#include <vector>

const int ARRAY_SIZE = 1000;
const int NUM_THREADS = 4;
int array[ARRAY_SIZE];
int partial_sum[NUM_THREADS] = {0};

void sumArray(int start, int end, int index) {
    for (int i = start; i < end; ++i) {
        partial_sum[index] += array[i];
    }
}

int main() {
    // Initialize the array with values
    for (int i = 0; i < ARRAY_SIZE; ++i) {
        array[i] = i + 1;
    }

    std::vector<std::thread> threads;
    int chunk_size = ARRAY_SIZE / NUM_THREADS;

    // Create threads to calculate partial sums
    for (int i = 0; i < NUM_THREADS; ++i) {
        int start = i * chunk_size;
        int end = (i + 1) * chunk_size;
        threads.push_back(std::thread(sumArray, start, end, i));
    }

    // Wait for all threads to finish
    for (auto& t : threads) {
        t.join();
    }

    // Calculate the total sum
    int total_sum = 0;
    for (int i = 0; i < NUM_THREADS; ++i) {
        total_sum += partial_sum[i];
    }

    std::cout << "Total sum: " << total_sum << std::endl;

    return 0;
}

Explanation

  • sumArray function: Calculates the sum of a portion of the array.
  • std::vector<std::thread> threads: Stores the threads.
  • threads.push_back(std::thread(sumArray, start, end, i)): Creates and starts a thread to calculate a partial sum.
  • t.join(): Waits for each thread to finish.
  • total_sum: Aggregates the partial sums to get the total sum.

Summary

In this section, we covered the basics of multithreading in C++, including creating and managing threads, synchronization using mutexes, and common pitfalls. We also provided a practical exercise to reinforce the concepts learned. Understanding and effectively using multithreading can significantly improve the performance and responsiveness of your applications.

© Copyright 2024. All rights reserved