C++ Multithreading: Thread Creation, Mutex, Synchronization, std::async & Best Practices

1. Multithreading Basics

Q: What is multithreading in C++?

Multithreading is a programming technique that allows multiple threads to run concurrently within a single program, enabling parallel execution of tasks. In C++, the <thread> library (introduced in C++11) provides tools to create and manage threads, improving performance for tasks like computations or I/O operations.

Q: What is a thread in C++?

A thread is a sequence of instructions that can execute independently within a program. Each thread shares the program’s memory but has its own stack and program counter. The std::thread class in C++ represents a single thread.

Q: How do you create a thread in C++?

Use the std::thread class from the <thread> library. Pass a function (or callable object) to the std::thread constructor to start the thread.

Syntax:

#include <thread>
void myFunction() { /* Task */ }
std::thread t(myFunction); // Create thread
t.join(); // Wait for thread to finish

Q: What is the difference between join() and detach() in C++ threads?

Q: Can you give an example of creating and using threads in C++?

#include <iostream>
#include <thread>
using namespace std;

void printMessage(const string& msg, int count) {
    for (int i = 0; i < count; ++i) {
        cout << msg << " " << i + 1 << endl;
    }
}

int main() {
    // Create two threads
    thread t1(printMessage, "Thread 1:", 3);
    thread t2(printMessage, "Thread 2:", 3);
    
    // Wait for threads to finish
    t1.join();
    t2.join();
    
    cout << "Main thread finished" << endl;
    return 0;
}

Output (order may vary due to thread scheduling):

Thread 1: 1
Thread 1: 2
Thread 1: 3
Thread 2: 1
Thread 2: 2
Thread 2: 3
Main thread finished

Q: What is thread synchronization, and why is it needed?

Thread synchronization ensures that multiple threads access shared resources (e.g., variables, files) safely to avoid data corruption or race conditions. It’s needed when threads share data, as unsynchronized access can lead to unpredictable results.

Q: What are common synchronization mechanisms in C++?

Q: Can you give an example of thread synchronization using a mutex?

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx; // Mutex for synchronization
int counter = 0; // Shared resource

void incrementCounter(const string& name, int times) {
    for (int i = 0; i < times; ++i) {
        mtx.lock(); // Lock the mutex
        ++counter;
        cout << name << " incremented counter to " << counter << endl;
        mtx.unlock(); // Unlock the mutex
    }
}

int main() {
    thread t1(incrementCounter, "Thread 1", 3);
    thread t2(incrementCounter, "Thread 2", 3);
    
    t1.join();
    t2.join();
    
    cout << "Final counter value: " << counter << endl;
    return 0;
}

Output (order may vary):

Thread 1 incremented counter to 1
Thread 1 incremented counter to 2
Thread 1 incremented counter to 3
Thread 2 incremented counter to 4
Thread 2 incremented counter to 5
Thread 2 incremented counter to 6
Final counter value: 6

Q: What is a lock guard, and how does it improve mutex usage?

A std::lock_guard is an RAII class that automatically locks a mutex when created and unlocks it when destroyed (e.g., goes out of scope). It prevents forgetting to unlock a mutex, especially during exceptions.

Example:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx;
int counter = 0;

void incrementCounter(const string& name, int times) {
    for (int i = 0; i < times; ++i) {
        lock_guard<mutex> lock(mtx); // Automatically locks/unlocks
        ++counter;
        cout << name << " incremented counter to " << counter << endl;
    }
}

int main() {
    thread t1(incrementCounter, "Thread 1", 3);
    thread t2(incrementCounter, "Thread 2", 3);
    
    t1.join();
    t2.join();
    
    cout << "Final counter value: " << counter << endl;
    return 0;
}

Output (order may vary):

Thread 1 incremented counter to 1
Thread 1 incremented counter to 2
Thread 2 incremented counter to 3
Thread 2 incremented counter to 4
Thread 2 incremented counter to 5
Thread 1 incremented counter to 6
Final counter value: 6

Q: What is a race condition, and how does it occur?

A race condition occurs when multiple threads access a shared resource simultaneously, and at least one modifies it, leading to unpredictable results. It’s prevented using synchronization mechanisms like mutexes.

Q: How does multithreading differ from C?

Q: What are common mistakes with multithreading in C++?

Q: What are best practices for multithreading in C++?

Q: What is std::async, and how does it relate to threads?

std::async (from <future>) is a higher-level abstraction that runs a function asynchronously, potentially in a separate thread, and returns a std::future to retrieve the result. It simplifies thread management by handling creation and joining automatically.

Example:

#include <iostream>
#include <future>
using namespace std;

int computeSum(int a, int b) {
    return a + b;
}

int main() {
    // Run computeSum asynchronously
    future<int> result = async(launch::async, computeSum, 5, 10);
    
    // Get result (blocks until ready)
    cout << "Sum: " << result.get() << endl; // Output: Sum: 15
    
    return 0;
}

Output:

Sum: 15