Multithreading in C++


Multithreading in C++ Interview with follow-up questions

1. What is multithreading in C++ and why is it used?

Multithreading in C++ is the ability of a program to execute multiple threads concurrently within a single process. A thread is the smallest unit of CPU scheduling — each thread has its own stack and program counter but shares the process's heap, globals, and file descriptors with other threads.

Why it is used:

  • Parallelism on multi-core hardware — CPU-bound work (image processing, physics simulations, data crunching) can be split across cores and run truly simultaneously.
  • Concurrency for responsiveness — a UI or server can keep responding to input or new connections while long-running work executes on a background thread.
  • Better CPU utilization — when one thread blocks on I/O, the OS can schedule another thread, keeping cores busy.
  • Decomposing independent tasks — producer/consumer pipelines, parallel map-reduce patterns, and async I/O all map naturally to threads.

C++ threading support (C++11 and later):

The header and related standard-library facilities (, ,, ``) provide portable threading without relying on platform APIs (pthreads, Win32 threads).

C++17 added parallel algorithms (std::execution::par, std::execution::par_unseq) that let you parallelize standard algorithms with a policy flag. C++20 added std::jthread, which joins automatically on destruction and supports cooperative cancellation via std::stop_token, removing a common resource-management mistake.

Key gotcha interviewers probe: multithreading introduces non-determinism. Any shared mutable state accessed by more than one thread without synchronization is a data race — undefined behavior in C++. This is why understanding synchronization primitives is inseparable from understanding threading.

↑ Back to top

Follow-up 1

Can you explain the difference between multithreading and multiprocessing?

Multithreading and multiprocessing are both techniques used to achieve parallelism, but they differ in how they utilize system resources.

Multithreading involves executing multiple threads within a single process. These threads share the same memory space and can communicate with each other directly. They can be scheduled by the operating system to run concurrently on multiple CPU cores, allowing for improved performance. However, if one thread encounters an error or crashes, it can potentially affect the stability of the entire process.

On the other hand, multiprocessing involves executing multiple processes, each with its own memory space. These processes do not share memory directly and communicate through inter-process communication mechanisms. While multiprocessing provides better isolation and stability, it also incurs more overhead due to the need for inter-process communication.

Follow-up 2

What are the advantages and disadvantages of multithreading?

Advantages of multithreading in C++ include:

  • Improved performance: Multithreading allows for parallel execution of tasks, making better use of CPU resources and potentially reducing the overall execution time.
  • Responsiveness: Multithreading can keep a program responsive by allowing it to perform multiple tasks simultaneously, such as handling user input while performing background computations.
  • Resource sharing: Threads within the same process can share memory and resources, making it easier to communicate and coordinate between different parts of a program.

Disadvantages of multithreading include:

  • Complexity: Multithreaded programs can be more complex to design, implement, and debug compared to single-threaded programs. Issues such as race conditions, deadlocks, and synchronization can arise.
  • Increased memory usage: Each thread requires its own stack and thread-specific data, which can increase memory usage compared to a single-threaded program.
  • Potential for bugs: Multithreading introduces the possibility of bugs related to thread synchronization, data sharing, and concurrent access to resources.

Follow-up 3

How does multithreading affect memory and CPU usage?

Multithreading can affect memory and CPU usage in the following ways:

  • Memory usage: Each thread within a process requires its own stack and thread-specific data. This can increase the memory usage compared to a single-threaded program. Additionally, if multiple threads share memory, proper synchronization mechanisms need to be in place to avoid data corruption or race conditions.
  • CPU usage: Multithreading can make better use of CPU resources by allowing multiple threads to execute concurrently on multiple CPU cores. This can lead to improved performance and faster execution of tasks. However, it is important to note that excessive multithreading can also lead to increased CPU usage, as the operating system needs to manage and schedule the execution of multiple threads.

2. How do you create a thread in C++?

In C++, threads are created using std::thread from the `` header (C++11 and later). You pass a callable — a free function, lambda, or function object — and any arguments to the constructor.

Basic example:

#include 
#include 

void greet(int id) {
    std::cout << "Hello from thread " << id << '\n';
}

int main() {
    std::thread t(greet, 42);
    t.join();   // wait for t to finish before main exits
    return 0;
}

Using a lambda (common in practice):

std::thread t([] {
    std::cout << "Hello from lambda thread\n";
});
t.join();

join vs detach:

  • join() blocks the calling thread until the spawned thread finishes. You must call either join or detach before the std::thread object is destroyed, or the program calls std::terminate.
  • detach() lets the thread run independently ("fire and forget"). The thread's lifetime is then managed by the runtime, but you lose the ability to synchronize with it later.

Prefer std::jthread (C++20):

std::jthread joins automatically in its destructor, eliminating the common bug of forgetting to call join. It also supports cooperative cancellation via std::stop_token:

#include 

int main() {
    std::jthread t([](std::stop_token st) {
        while (!st.stop_requested()) {
            // do work
        }
    });
    // t.request_stop() + automatic join happen at scope exit
}

Passing arguments by reference: std::thread copies arguments by default. Use std::ref or std::cref to pass by reference, but ensure the referenced object outlives the thread.

int result = 0;
std::thread t([&result] { result = expensive_computation(); });
t.join();
// result is safe to read here
↑ Back to top

Follow-up 1

What are the different ways to pass arguments to a thread?

There are several ways to pass arguments to a thread in C++:

  1. Pass by value: You can pass arguments to a thread by value. The thread function will receive a copy of the argument.

  2. Pass by reference: You can pass arguments to a thread by reference. The thread function will receive a reference to the argument.

  3. Pass by move: You can pass arguments to a thread by using std::move. This is useful when you want to transfer ownership of a resource to the thread.

Here's an example that demonstrates these different ways:

#include 
#include 

void myFunction(int value, int& refValue, std::string&& moveValue) {
    std::cout << "Value: " << value << std::endl;
    std::cout << "Reference Value: " << refValue << std::endl;
    std::cout << "Move Value: " << moveValue << std::endl;
}

int main() {
    int value = 42;
    int refValue = 100;
    std::string moveValue = "Hello";

    std::thread myThread(myFunction, value, std::ref(refValue), std::move(moveValue));
    myThread.join();

    std::cout << "Value after thread: " << value << std::endl;
    std::cout << "Reference Value after thread: " << refValue << std::endl;
    std::cout << "Move Value after thread: " << moveValue << std::endl;

    return 0;
}

In this example, we pass value by value, refValue by reference using std::ref, and moveValue by move using std::move.

Follow-up 2

How do you handle exceptions in a thread?

When a thread throws an uncaught exception, the program terminates. To handle exceptions in a thread, you can use a try-catch block inside the thread function. Here's an example:

#include 
#include 

void myFunction() {
    try {
        throw std::runtime_error("Exception in thread!");
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
}

int main() {
    std::thread myThread(myFunction);
    myThread.join();
    return 0;
}

In this example, the thread function myFunction throws a std::runtime_error. We catch the exception inside the thread function and print the error message. If we didn't catch the exception, the program would terminate.

Follow-up 3

Can you explain the life cycle of a thread in C++?

The life cycle of a thread in C++ can be summarized in the following stages:

  1. Creation: A thread is created using the std::thread constructor.

  2. Execution: The thread starts executing the thread function specified in the constructor.

  3. Running: The thread is actively running and executing the thread function.

  4. Termination: The thread function completes execution or is terminated prematurely.

  5. Joining: The main thread waits for the thread to finish execution using the join function.

  6. Destruction: The std::thread object is destroyed.

It's important to note that a thread can be detached from the main thread using the detach function. In this case, the main thread does not wait for the thread to finish execution, and the std::thread object is automatically destroyed when the thread finishes.

3. What is thread synchronization and why is it important?

Thread synchronization is the coordination of multiple threads so that they access shared resources in a controlled, correct order. Without it, concurrent reads and writes to the same memory location produce a data race, which is undefined behavior in C++ — the compiler and CPU are free to reorder or optimize in ways that assume no concurrent access.

Why it matters:

  • Data races corrupt state in ways that are hard to reproduce (the bug may disappear under a debugger).
  • Race conditions (logical timing bugs) can cause incorrect results even when individual memory accesses are safe.
  • Deadlock can occur if synchronization is applied incorrectly (two threads each hold a lock the other needs).

Primary synchronization tools in C++:

Tool Use case
std::mutex Mutual exclusion — only one thread enters a critical section at a time
std::lock_guard / std::scoped_lock RAII wrappers that automatically release the mutex
std::unique_lock Like lock_guard but unlockable mid-scope; required for condition variables
std::condition_variable Block a thread until a condition becomes true
std::atomic Lock-free operations on simple types; avoids mutex overhead for flags/counters
std::shared_mutex (C++17) Multiple readers, exclusive writer (reader-writer lock)

Idiomatic example — protecting shared state:

#include 
#include 

int counter = 0;
std::mutex mtx;

void increment(int n) {
    for (int i = 0; i < n; ++i) {
        std::lock_guard lock(mtx);  // released when lock goes out of scope
        ++counter;
    }
}

C++17 std::scoped_lock can lock multiple mutexes simultaneously without deadlock (uses deadlock-avoidance algorithm):

std::scoped_lock lock(mtx1, mtx2);

Key interview gotcha: holding a mutex for too long kills concurrency. Prefer fine-grained locking, or use std::atomic for simple counters and flags where a full mutex is unnecessary overhead.

↑ Back to top

Follow-up 1

What are the different ways to achieve thread synchronization in C++?

There are several ways to achieve thread synchronization in C++:

  1. Mutexes: Mutexes are used to protect shared resources by allowing only one thread to access them at a time. Threads can lock and unlock a mutex to ensure exclusive access.

  2. Condition Variables: Condition variables are used to synchronize the execution of threads based on certain conditions. Threads can wait on a condition variable until a specific condition is met.

  3. Semaphores: Semaphores are used to control access to a shared resource by limiting the number of threads that can access it simultaneously.

  4. Atomic Operations: Atomic operations provide a way to perform operations on shared variables in a way that is guaranteed to be atomic, without the need for explicit locking.

  5. Read-Write Locks: Read-write locks allow multiple threads to read a shared resource simultaneously, but only one thread can write to it at a time.

Follow-up 2

What is a deadlock and how can it be avoided?

A deadlock is a situation where two or more threads are blocked indefinitely, waiting for each other to release resources. Deadlocks can occur when multiple threads acquire locks on shared resources in different orders. To avoid deadlocks, the following strategies can be used:

  1. Avoid circular dependencies: Ensure that threads always acquire locks on shared resources in the same order.

  2. Use timeouts: Set a timeout for acquiring locks and release them if the timeout is exceeded.

  3. Use resource allocation graphs: Analyze the dependencies between threads and resources to identify potential deadlocks.

  4. Use deadlock detection algorithms: Implement algorithms that can detect and resolve deadlocks if they occur.

Follow-up 3

What is a race condition and how can it be prevented?

A race condition is a situation where the behavior of a program depends on the relative timing of events, such as the order in which threads are scheduled to run. Race conditions can lead to unpredictable and incorrect results. To prevent race conditions, the following techniques can be used:

  1. Use synchronization primitives: Use mutexes, condition variables, semaphores, or atomic operations to ensure that only one thread can access shared resources at a time.

  2. Use thread-safe data structures: Use data structures that are designed to be accessed by multiple threads concurrently, such as thread-safe queues or containers.

  3. Use thread-local storage: Use thread-local storage to store thread-specific data, eliminating the need for shared resources.

  4. Use message passing: Use message passing between threads to communicate and coordinate their actions, instead of relying on shared memory.

  5. Use immutable data: Use immutable data whenever possible, as it eliminates the need for synchronization.

4. What are the different states of a thread in C++?

The C++ standard describes thread state in terms of what std::thread objects can observe, rather than exposing an OS-level state machine. In practice, interviewers expect you to describe both the standard-library view and the underlying execution states:

  1. Not-a-thread (default-constructed / moved-from): A std::thread object that does not represent any OS thread. joinable() returns false.

  2. Runnable / Ready: The thread has been created and is eligible to run, but the OS scheduler has not yet given it CPU time.

  3. Running: The thread is actively executing on a CPU core.

  4. Blocked / Waiting: The thread is suspended, waiting for a resource or event — e.g., waiting to acquire a std::mutex, waiting on a std::condition_variable, or blocked on I/O. It consumes no CPU while blocked.

  5. Joinable (finished, not yet joined): The thread's function has returned, but join() has not been called. The std::thread object is still joinable — you must call join() or detach() before it is destroyed, or the destructor calls std::terminate().

  6. Terminated / Joined: join() has been called and returned (or detach() was called). The std::thread object is no longer joinable.

std::jthread (C++20) adds a stop-requested pseudo-state: the thread can check stop_token::stop_requested() to cooperatively exit early when another thread calls request_stop().

Key interview point: C++ itself has no Thread.getState() API equivalent. If you need to know whether a thread is still alive, you design around joinable(), std::atomic flags, std::future / std::promise, or std::stop_token (C++20) rather than polling an OS state.

↑ Back to top

Follow-up 1

What happens when a thread is in a blocked state?

When a thread is in a blocked state, it means that it is waiting for a resource or event to become available. The thread will remain in this state until the resource or event it is waiting for is available. Once the resource or event becomes available, the thread will transition back to the runnable state and will be eligible to run again.

Follow-up 2

How does a thread transition between different states?

A thread can transition between different states in the following ways:

  1. New to Runnable: When a thread is created, it starts in the new state. It can transition to the runnable state when the operating system schedules it for execution.
  2. Runnable to Running: When a thread is in the runnable state, it can transition to the running state when the CPU is allocated to it.
  3. Running to Blocked: While a thread is running, it can transition to the blocked state if it needs to wait for a resource or event.
  4. Blocked to Runnable: Once the resource or event becomes available, a blocked thread can transition back to the runnable state.
  5. Running to Terminated: A running thread can transition to the terminated state when it finishes its execution.

The actual transition between states is managed by the operating system and the thread scheduler.

Follow-up 3

What is the difference between a daemon thread and a user thread?

In C++, there are two types of threads: daemon threads and user threads.

  1. Daemon Thread: A daemon thread is a background thread that runs in the background and does not prevent the program from exiting. If all user threads have finished their execution, the program can exit even if there are daemon threads still running. Daemon threads are typically used for tasks that need to run continuously in the background, such as garbage collection or logging.

  2. User Thread: A user thread is a thread that is created by the user and is not a daemon thread. User threads are the main threads that perform the program's main tasks. If a user thread is still running, the program will not exit even if all other user threads have finished their execution.

5. What is the role of the thread library in C++?

The C++ thread library — spread across several standard headers — provides everything needed to write portable concurrent programs without platform-specific APIs.

Core headers and what they provide:

Header Contents
`` std::thread, std::jthread (C++20), std::this_thread utilities
`` std::mutex, std::recursive_mutex, std::timed_mutex, std::lock_guard, std::unique_lock, std::scoped_lock (C++17)
`` std::shared_mutex, std::shared_lock (reader-writer locking, C++17)
`` std::condition_variable, std::condition_variable_any
`` std::atomic, std::atomic_ref (C++20), memory-ordering constants
`` std::future, std::promise, std::packaged_task, std::async
/ One-time and reusable thread-coordination barriers (C++20)
`` std::counting_semaphore, std::binary_semaphore (C++20)
`` std::stop_token, std::stop_source for cooperative cancellation (C++20)

Highlights by use case:

  • Launching work: std::thread / std::jthread for explicit threads; std::async for task-based concurrency that returns a std::future.
  • Mutual exclusion: std::mutex + std::lock_guard or std::scoped_lock.
  • Signaling: std::condition_variable to wake waiting threads when state changes.
  • Lock-free coordination: std::atomic with appropriate memory orders (relaxed, acquire, release, seq_cst).
  • One-shot barriers: std::latch (C++20) — a thread-safe countdown that lets a group of threads wait until all have reached a point.
  • Cancellation: std::stop_token (C++20) — the idiomatic way to request cooperative thread shutdown.

Parallel algorithms (C++17): std::execution::par and std::execution::par_unseq policies on standard algorithms (std::sort, std::transform, etc.) let the library parallelize work automatically — no manual thread management needed for many common patterns.

The library abstracts OS differences (pthreads on POSIX, Win32 threads on Windows), so code is fully portable across platforms when you use these facilities.

↑ Back to top

Follow-up 1

What are some of the key functions provided by the thread library?

Some of the key functions provided by the thread library in C++ include:

  • std::thread: This class represents a single thread of execution. You can create a new thread by instantiating an object of this class and passing a function or callable object to be executed in the new thread.

  • std::this_thread::get_id(): This function returns the unique identifier of the current thread.

  • std::thread::join(): This function blocks the current thread until the thread represented by the std::thread object has completed its execution.

  • std::thread::detach(): This function allows the thread represented by the std::thread object to continue running independently from the thread that created it.

  • std::thread::sleep_for(): This function suspends the execution of the current thread for a specified duration.

These are just a few examples, and there are many more functions available in the thread library.

Follow-up 2

How do you use the thread library to manage multiple threads?

To use the thread library to manage multiple threads in C++, you can follow these steps:

  1. Include the `` header in your program.

  2. Create a function or callable object that represents the task you want each thread to execute.

  3. Instantiate a std::thread object, passing the function or callable object as a parameter.

  4. Start the thread by calling the std::thread::join() function.

  5. Repeat steps 3 and 4 for each additional thread you want to create.

By creating multiple std::thread objects and starting them, you can manage multiple threads of execution in your program.

Follow-up 3

What is thread-local storage in C++?

Thread-local storage in C++ allows you to declare variables that are local to each thread. Each thread has its own copy of the variable, and changes made to the variable in one thread do not affect the value of the variable in other threads. This can be useful when you need to store thread-specific data or when you want to avoid synchronization issues that can arise when multiple threads access the same variable simultaneously.

To declare a thread-local variable in C++, you can use the thread_local keyword. For example:

thread_local int threadId;

In this example, threadId is a thread-local variable of type int. Each thread will have its own copy of threadId, and changes made to threadId in one thread will not affect the value of threadId in other threads.

Live mock interview

Mock interview: Multithreading in C++

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.