Multithreading Interview Question
Multithreading Interview Question

Multithreading Interview Question

Basic Questions

1. What is a mutex in C++ threading, and how does it work?

A mutex (short for mutual exclusion) is a synchronization primitive in C++ that prevents multiple threads from accessing shared resources simultaneously. It ensures that only one thread can access a critical section at a time, avoiding race conditions.

How It Works:

  • A thread locks a mutex before entering a critical section.
  • Other threads trying to lock the same mutex will be blocked until the first thread releases the mutex.
  • When the thread is done, it unlocks the mutex, allowing other waiting threads to proceed.
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // Mutex declaration

void printHello(int id) {
    mtx.lock();  // Lock the mutex
    std::cout << "Hello from thread " << id << std::endl;
    mtx.unlock(); // Unlock the mutex
}

int main() {
    std::thread t1(printHello, 1);
    std::thread t2(printHello, 2);

    t1.join();
    t2.join();
    return 0;
}

Here, mtx.lock() ensures that only one thread prints at a time, avoiding mixed-up outputs.

2. What is the difference between std::mutex, std::recursive_mutex, std::timed_mutex, and std::shared_mutex?

TypeDescription
std::mutexBasic mutex, allows only one thread to own the lock at a time.
std::recursive_mutexAllows the same thread to lock the mutex multiple times (useful for recursive functions).
std::timed_mutexSimilar to std::mutex, but supports timeout-based locking.
std::shared_mutexAllows multiple threads to read simultaneously but only one to write (used for reader-writer locks).

Example of std::recursive_mutex

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

std::recursive_mutex rmtx;

void recursiveFunction(int count) {
    if (count == 0) return;
    
    rmtx.lock();
    std::cout << "Thread executing recursive function: " << count << std::endl;
    recursiveFunction(count - 1);
    rmtx.unlock();
}

int main() {
    std::thread t1(recursiveFunction, 3);
    t1.join();
    return 0;
}

Without std::recursive_mutex, this would result in a deadlock when recursiveFunction calls itself.

3. How do you lock and unlock a std::mutex in C++?

You can lock and unlock a std::mutex using:

  • lock() to acquire the mutex.
  • unlock() to release the mutex.
  • std::lock_guard or std::unique_lock to manage locking automatically.

Example using std::lock_guard (RAII-based approach)

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

std::mutex mtx;

void printMessage(int id) {
    std::lock_guard<std::mutex> lock(mtx); // Automatically locks & unlocks
    std::cout << "Thread " << id << " is executing\n";
}

int main() {
    std::thread t1(printMessage, 1);
    std::thread t2(printMessage, 2);
    t1.join();
    t2.join();
    return 0;
}

Here, std::lock_guard ensures that the mutex is released automatically when the function exits.

4. What are deadlocks, and how can they occur in multithreaded programs?

A deadlock occurs when two or more threads are waiting indefinitely for each other to release a resource, causing a circular wait condition.

Example of a Deadlock

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

std::mutex mtx1, mtx2;

void task1() {
    mtx1.lock();
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
    mtx2.lock(); // Waiting for mtx2 (held by task2)
    std::cout << "Task 1 executing\n";
    mtx2.unlock();
    mtx1.unlock();
}

void task2() {
    mtx2.lock();
    std::this_thread::sleep_for(std::chrono::milliseconds(100)); 
    mtx1.lock(); // Waiting for mtx1 (held by task1)
    std::cout << "Task 2 executing\n";
    mtx1.unlock();
    mtx2.unlock();
}

int main() {
    std::thread t1(task1);
    std::thread t2(task2);
    t1.join();
    t2.join();
    return 0;
}

Here, both threads wait for each other to release a mutex, leading to a deadlock.

5. How can you avoid deadlocks while using multiple mutexes?

You can avoid deadlocks using these techniques:

1. Always lock mutexes in a fixed order

void task1() {
    std::lock(mtx1, mtx2); // Lock both mutexes in one go
    std::lock_guard<std::mutex> lg1(mtx1, std::adopt_lock);
    std::lock_guard<std::mutex> lg2(mtx2, std::adopt_lock);
}

Here, std::lock() locks both mutexes simultaneously, preventing deadlock.

2. Use std::try_lock to avoid waiting indefinitely

void task1() {
    if (mtx1.try_lock()) {
        if (mtx2.try_lock()) {
            // Critical section
            mtx2.unlock();
        }
        mtx1.unlock();
    }
}

If mtx2 is already locked, the thread releases mtx1 and retries.

3. Use std::unique_lock for more flexibility

void task1() {
    std::unique_lock<std::mutex> lk1(mtx1, std::defer_lock);
    std::unique_lock<std::mutex> lk2(mtx2, std::defer_lock);
    std::lock(lk1, lk2); // Lock both mutexes safely
}

Here, std::defer_lock postpones locking, and std::lock() acquires both safely.

Intermediate Questions:

1. What is the purpose of std::unique_lock, and how does it differ from std::lock_guard?

Purpose of std::unique_lock

std::unique_lock is a flexible mutex wrapper that provides advanced locking mechanisms, such as:

  • Deferred locking (lock the mutex later)
  • Timed locking (lock with a timeout)
  • Lock ownership transfer (move lock ownership between functions)

Difference Between std::unique_lock and std::lock_guard

Featurestd::lock_guardstd::unique_lock
Locking behaviorAlways locks the mutex upon creationCan defer locking, lock later, or use timed locking
Unlock flexibilityNo manual unlock; unlocks on destructionCan unlock manually before destruction
PerformanceFaster, as it has no extra overheadSlightly slower due to added flexibility
Moveable❌ No✅ Yes (can transfer ownership)

Example of std::unique_lock

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

std::mutex mtx;

void task() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // Defer locking
    // Do some work before locking
    lock.lock();
    std::cout << "Thread executing\n";
    lock.unlock(); // Manually unlock before function exits
}

int main() {
    std::thread t1(task);
    t1.join();
    return 0;
}

Here, std::defer_lock allows the mutex to be locked later when needed.

2. What are condition variables in C++, and how are they used for thread synchronization?

Purpose of Condition Variables

Condition variables allow threads to wait for a certain condition to be met without busy-waiting. They help coordinate communication between threads.

Key Methods

  • wait(lock, predicate) → Waits until predicate is true.
  • notify_one() → Wakes up one waiting thread.
  • notify_all() → Wakes up all waiting threads.

Example of Condition Variables

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

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return ready; }); // Wait until ready is true
    std::cout << "Worker thread proceeding\n";
}

void signal() {
    std::lock_guard<std::mutex> lock(mtx);
    ready = true;
    cv.notify_one(); // Notify worker thread
}

int main() {
    std::thread t1(worker);
    std::this_thread::sleep_for(std::chrono::seconds(1)); // Simulate some work
    signal();
    t1.join();
    return 0;
}

Here, the worker thread waits for the ready flag to be true, and the main thread signals it to proceed.

3. How does std::shared_mutex work, and when should you use it?

Purpose of std::shared_mutex

std::shared_mutex allows:

  • Multiple readers to access a shared resource simultaneously.
  • Only one writer to modify the resource at a time.

Use Case

Use std::shared_mutex when you have multiple readers but only one writer, such as a cache or a database read operation.

Example of std::shared_mutex

#include <iostream>
#include <thread>
#include <shared_mutex>

std::shared_mutex smtx;

void reader(int id) {
    std::shared_lock<std::shared_mutex> lock(smtx); // Multiple readers allowed
    std::cout << "Reader " << id << " is reading\n";
}

void writer() {
    std::unique_lock<std::shared_mutex> lock(smtx); // Only one writer allowed
    std::cout << "Writer is writing\n";
}

int main() {
    std::thread r1(reader, 1);
    std::thread r2(reader, 2);
    std::thread w1(writer);

    r1.join();
    r2.join();
    w1.join();
    return 0;
}

Here, multiple reader() threads can execute concurrently, but only one writer() thread is allowed at a time.

4. What is a race condition? How can mutexes help prevent race conditions?

What is a Race Condition?

A race condition occurs when multiple threads access and modify shared data simultaneously, leading to unpredictable behavior.

Example of a Race Condition

#include <iostream>
#include <thread>

int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter; // No synchronization → Race condition!
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Final counter value: " << counter << std::endl; // Unpredictable output!
    return 0;
}

Since both threads modify counter simultaneously, the result is incorrect.

How Mutexes Prevent Race Conditions

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

int counter = 0;
std::mutex mtx;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter; // Now protected
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Final counter value: " << counter << std::endl; // Correct output
    return 0;
}

Here, std::lock_guard ensures only one thread at a time modifies counter, preventing race conditions.

5. What is std::defer_lock, and when would you use it?

What is std::defer_lock?

std::defer_lock allows creating a lock object without locking the mutex immediately. You can lock it later when needed.

When to Use It?

  • When you want to lock multiple mutexes safely (avoiding deadlocks).
  • When the lock scope needs to be controlled manually.
  • When you need to check conditions before locking.

Example Using std::defer_lock

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

std::mutex mtx;

void task() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // Defer locking
    // Perform some work before locking
    std::cout << "Doing work before locking...\n";
    lock.lock();  // Lock only when necessary
    std::cout << "Thread executing critical section\n";
    lock.unlock(); // Unlock manually if needed
}

int main() {
    std::thread t1(task);
    t1.join();
    return 0;
}

Here, std::defer_lock allows delaying the lock until it’s actually needed.

🔹 Summary Table

ConceptExplanation
std::unique_lock vs std::lock_guardstd::unique_lock is more flexible (deferred locking, timed locking, ownership transfer), whereas std::lock_guard is simpler and faster.
Condition VariablesUsed for thread synchronization, allowing threads to wait until a condition is met (wait(), notify_one(), notify_all()).
std::shared_mutexAllows multiple readers and one writer, useful for read-heavy workloads.
Race ConditionOccurs when multiple threads modify shared data simultaneously; mutexes help prevent this.
std::defer_lockAllows creating a lock without locking immediately; useful for complex locking scenarios.

Advanced Questions:

1. Explain the RAII Principle in the Context of Mutex Handling.

What is RAII?

RAII (Resource Acquisition Is Initialization) is a C++ programming principle where resources (like memory, file handles, and mutexes) are acquired in a constructor and released in a destructor. This ensures that resources are properly managed and automatically cleaned up when an object goes out of scope.

RAII in Mutex Handling

When using mutexes, RAII ensures:

  • The mutex locks when an object is created.
  • The mutex unlocks automatically when the object goes out of scope (even if an exception occurs).

RAII-Based Mutex Handling with std::lock_guard

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

std::mutex mtx;

void safe_function() {
    std::lock_guard<std::mutex> lock(mtx); // Mutex locked here
    std::cout << "Critical section\n";
} // Mutex unlocked automatically when `lock` goes out of scope

int main() {
    std::thread t1(safe_function);
    std::thread t2(safe_function);
    t1.join();
    t2.join();
    return 0;
}

💡 Why Use RAII for Mutexes?

  • Prevents forgetting to unlock a mutex.
  • Handles exceptions safely (ensures mutex is released even if an exception is thrown).
  • Simplifies code by reducing the need for manual lock() and unlock() calls.

2. How Does std::scoped_lock Help Prevent Deadlocks?

What is std::scoped_lock?

std::scoped_lock (C++17) is a RAII-based mutex wrapper that locks multiple mutexes safely to prevent deadlocks.

How It Prevents Deadlocks

Deadlocks occur when two threads lock multiple mutexes in different orders. std::scoped_lock automatically locks all mutexes in a consistent order, preventing deadlocks.

Example of std::scoped_lock Preventing Deadlocks

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

std::mutex mtx1, mtx2;

void thread1() {
    std::scoped_lock lock(mtx1, mtx2); // Locks both mutexes in a safe order
    std::cout << "Thread 1 executing\n";
}

void thread2() {
    std::scoped_lock lock(mtx1, mtx2); // Prevents deadlock
    std::cout << "Thread 2 executing\n";
}

int main() {
    std::thread t1(thread1);
    std::thread t2(thread2);
    t1.join();
    t2.join();
    return 0;
}

💡 Key Benefits of std::scoped_lock:

  • Locks multiple mutexes safely without causing deadlocks.
  • RAII-based, so mutexes are automatically released.
  • Simplifies complex locking logic.

3. What is the Impact of Lock Contention in Multithreaded Programs? How Can You Minimize It?

What is Lock Contention?

Lock contention occurs when multiple threads compete for the same mutex, leading to delays as threads must wait for the mutex to be available.

Impact of Lock Contention

  • Increased latency (threads spend more time waiting).
  • Reduced parallelism (threads are blocked).
  • Performance bottlenecks in CPU-intensive applications.

How to Minimize Lock Contention?

TechniqueDescription
Reduce Critical Section SizeMinimize the time spent holding a lock.
Use Read-Write Locks (std::shared_mutex)Allow multiple readers while restricting writes.
Use Fine-Grained LockingLock only the necessary data instead of a global lock.
Avoid Unnecessary LocksCheck if locking is required before acquiring a mutex.
Use Lock-Free Data StructuresUtilize atomic operations (std::atomic) where possible.
Use Try-Lock (std::mutex::try_lock)Attempt to acquire the lock without blocking.

Example: Using std::shared_mutex to Reduce Lock Contention

#include <iostream>
#include <thread>
#include <shared_mutex>

std::shared_mutex smtx;
int shared_data = 0;

void reader(int id) {
    std::shared_lock<std::shared_mutex> lock(smtx); // Multiple readers allowed
    std::cout << "Reader " << id << " read value: " << shared_data << "\n";
}

void writer() {
    std::unique_lock<std::shared_mutex> lock(smtx); // Only one writer allowed
    shared_data += 10;
    std::cout << "Writer updated value to " << shared_data << "\n";
}

int main() {
    std::thread r1(reader, 1);
    std::thread r2(reader, 2);
    std::thread w1(writer);

    r1.join();
    r2.join();
    w1.join();
    return 0;
}

Here, multiple readers can execute concurrently, reducing lock contention.

4. How Does std::call_once and std::once_flag Work in C++?

Purpose

std::call_once ensures that a function runs only once, even in a multithreaded environment.

How It Works

  • std::once_flag → Stores whether a function has been executed.
  • std::call_once → Runs a function only once, no matter how many threads call it.

Example of std::call_once

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

std::once_flag flag;

void initialize() {
    std::call_once(flag, [] {
        std::cout << "Initialization function running only once\n";
    });
}

int main() {
    std::thread t1(initialize);
    std::thread t2(initialize);
    std::thread t3(initialize);

    t1.join();
    t2.join();
    t3.join();
    return 0;
}

💡 Benefits:

  • Ensures thread-safe singleton initialization.
  • Prevents duplicate initialization of resources.

5. Explain Spinlocks and Compare Them with Mutexes in Terms of Performance and Use Cases.

What is a Spinlock?

A spinlock is a type of lock where a thread continuously checks (spins) until the lock is available instead of sleeping.

Spinlock vs. Mutex

FeatureSpinlockMutex
BlockingSpins (busy-waits)Puts thread to sleep
Context SwitchingNo (efficient for short waits)Yes (expensive)
CPU UsageHigh (wastes CPU cycles)Low (CPU-efficient)
Use CaseShort critical sections, low contentionLong critical sections, high contention

Example of a Simple Spinlock

#include <iostream>
#include <atomic>
#include <thread>

class Spinlock {
    std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
    void lock() {
        while (flag.test_and_set(std::memory_order_acquire)) { /* spin */ }
    }
    void unlock() {
        flag.clear(std::memory_order_release);
    }
};

Spinlock spinlock;
int counter = 0;

void increment() {
    spinlock.lock();
    ++counter;
    spinlock.unlock();
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Counter: " << counter << "\n";
    return 0;
}

💡 Use Spinlocks When:

  • Lock contention is low.
  • Critical section execution is very short.
  • You want to avoid context switching overhead.

General Conceptual Questions

  1. What is the difference between a mutex and a semaphore?
  2. What is the difference between a spinlock and a mutex?
  3. What are the types of mutexes available in C++?
  4. What is a critical section, and how does a mutex help protect it?
  5. What are the differences between a recursive mutex and a normal mutex?
  6. How does a read-write lock (shared_mutex) work in C++?
  7. What is the difference between optimistic and pessimistic concurrency control?

Mutex-Specific Questions

  1. What happens if a thread tries to lock a mutex twice?
  2. What is std::unique_lock, and how is it different from std::lock_guard?
  3. When would you use std::scoped_lock in C++?
  4. What happens if a mutex is not unlocked properly?
  5. What is a try-lock mechanism, and how does it work?
  6. How do you avoid unnecessary locking in a multi-threaded application?
  7. What is the purpose of std::call_once and std::once_flag?

Deadlock-Specific Questions

  1. What are the four necessary conditions for a deadlock to occur?
  2. What is circular wait, and how can it be avoided?
  3. How does using std::lock() prevent deadlocks?
  4. What is a livelock, and how is it different from a deadlock?
  5. How can you detect and recover from a deadlock?
  6. What are some common strategies to prevent deadlocks in C++?
  7. Explain deadlock prevention vs. deadlock avoidance techniques.

Race Condition & Synchronization Questions

  1. What is a race condition, and why does it occur?
  2. What is std::atomic, and how does it help prevent race conditions?
  3. What are the differences between std::mutex and std::atomic?
  4. Can a race condition occur even when using a mutex?
  5. How can condition variables help prevent race conditions?
  6. What is false sharing in multithreading, and how does it affect performance?
  7. What is memory reordering, and how can it cause race conditions?

Advanced Multithreading & Performance Questions

  1. What are thread-safe data structures, and how do they help in concurrency?
  2. How does the C++ memory model ensure thread safety?
  3. What are lock-free data structures, and when should they be used?
  4. What are the differences between user-space and kernel-space threading?
  5. What are thread pools, and how do they improve performance?
  6. How does priority inversion occur, and how can it be handled?
  7. What is the ABA problem in multithreading?

Practical Coding Questions

  1. Write a thread-safe singleton using std::mutex.
  2. Write a multi-threaded producer-consumer program using condition variables.
  3. Write a program that uses std::shared_mutex for read-write access.
  4. Write a function that detects deadlock in a multi-threaded program.
  5. Write a function that simulates a bank transaction system using multiple threads.

Thank you for exploring this tutorial! Stay ahead in embedded systems with expert insights, hands-on projects, and in-depth guides. Follow Embedded Prep for the latest trends, best practices, and step-by-step tutorials to enhance your expertise. Keep learning, keep innovating!

You can also Visit .

Spread the knowledge with embedded prep
Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *