MULTITHREADING
MULTITHREADING
📘 Topics to Learn
What is a thread?
std::thread basics
Race conditions
Condition variables
std::condition_variable
Producer-Consumer Problem
std::future , std::async
Atomic operations
✅
MULTITHREADING 1
✅Questions
Multithreading in C++: Roadmap with Coding
MULTITHREADING 2
🔹 Stage 4: Thread Pool
💻 Coding Questions
1. Create a basic thread pool in C++ with worker threads.
LEETCODE
# Problem Title Difficulty Topics
Mutex, Condition
2 1115. Print FooBar Alternately Medium
Variable
Barrier, Condition
4 1195. Fizz Buzz Multithreaded Medium
Variable
MULTITHREADING 3
1242. Web Crawler
8 Medium DFS, Thread-safe Set
Multithreaded
Semaphore, Thread
10 Building H2O (LeetCode 1117) Medium
coordination
✅ 1. What is a thread?
A thread is the smallest unit of execution within a process. It shares the same
memory space as other threads in the same process, which allows for efficient
communication but also requires synchronization to avoid race conditions.
✅ 2. std::thread basics
C++11 introduced std::thread to launch threads:
#include <thread>
void func() { /* work */ }
std::thread t(func); // Thread starts
t.join(); // Wait for thread to finish
detach(): Runs independently; you cannot wait or get its result. Dangerous if
thread accesses shared data after main thread exits.
std::thread t(func);
t.join(); // Safe
MULTITHREADING 4
// or
t.detach(); // Fire and forget
✅ 5. Race conditions
Occurs when two or more threads access shared data and try to change it
simultaneously without synchronization.
Example:
int counter = 0;
void increment() { ++counter; }
std::mutex mtx;
void safe_increment() {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
MULTITHREADING 5
std::unique_lock : More flexible than lock_guard , supports lock() , unlock() , defer_lock ,
etc.
Prevention:
std::lock(mtx1, mtx2);
✅ 9. Condition variables
Used to synchronize threads based on certain conditions.
✅ 10. std::condition_variable
Works with std::unique_lock to notify threads.
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
void wait_thread() {
std::unique_lock<std::mutex> lock(mtx);
MULTITHREADING 6
cv.wait(lock, []{ return ready; });
}
std::queue<int> q;
std::mutex m;
std::condition_variable cv;
bool done = false;
void producer() {
std::unique_lock<std::mutex> lock(m);
q.push(1);
cv.notify_one();
}
void consumer() {
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [] { return !q.empty(); });
int val = q.front(); q.pop();
}
MULTITHREADING 7
✅ 14. Designing a thread pool
Involves:
A task queue
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread([&]{ prom.set_value(42); }).detach();
std::cout << fut.get(); // 42
std::atomic<int> a(0);
a++;
concurrent_queue in TBB
Each thread has its own copy—great for caching thread-specific data.
Let me know if you want C++ code examples for any of these explanations or a
cheat sheet-style PDF of this summary!
Here are the complete C++ programs for each of your problems with main()
MULTITHREADING 9
#include <iostream>
#include <thread>
void sayHello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t(sayHello);
t.join(); // Wait for thread to finish
return 0;
}
void printNumber(int n) {
std::cout << "Thread " << std::this_thread::get_id() << " prints: " << n << st
d::endl;
}
int main() {
std::vector<std::thread> threads;
MULTITHREADING 10
return 0;
}
#include <iostream>
#include <thread>
#include <chrono>
int main() {
std::thread t1(task, "Joined");
std::thread t2(task, "Detached");
MULTITHREADING 11
void printID() {
std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
}
int main() {
std::vector<std::thread> threads;
return 0;
}
✅threads.
5. Use std::thread::hardware_concurrency() to find available
#include <iostream>
#include <thread>
int main() {
unsigned int cores = std::thread::hardware_concurrency();
std::cout << "System supports " << cores << " concurrent threads." << st
d::endl;
return 0;
}
MULTITHREADING 12
✅synchronization).
1. Implement a counter using multiple threads (without
#include <iostream>
#include <thread>
#include <vector>
int counter = 0;
void increment() {
for (int i = 0; i < 1000; ++i) {
++counter;
}
}
int main() {
std::vector<std::thread> threads;
std::cout << "Counter (without sync): " << counter << std::endl;
return 0;
}
MULTITHREADING 13
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
int counter = 0;
std::mutex mtx;
void increment() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
++counter;
}
}
int main() {
std::vector<std::thread> threads;
std::cout << "Counter (with mutex): " << counter << std::endl;
return 0;
}
MULTITHREADING 14
✅ 3. Demonstrate deadlock with two mutexes.
#include <iostream>
#include <thread>
#include <mutex>
void task1() {
std::lock_guard<std::mutex> lock1(mtx1);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock2(mtx2); // Deadlock here
std::cout << "Task 1 acquired both locks.\n";
}
void task2() {
std::lock_guard<std::mutex> lock2(mtx2);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lock1(mtx1); // Deadlock here
std::cout << "Task 2 acquired both locks.\n";
}
int main() {
std::thread t1(task1);
std::thread t2(task2);
t1.join();
t2.join();
MULTITHREADING 15
✅ 4. Fix deadlock using std::lock() .
#include <iostream>
#include <thread>
#include <mutex>
void task1() {
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Task 1 acquired both locks safely.\n";
}
void task2() {
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
std::cout << "Task 2 acquired both locks safely.\n";
}
int main() {
std::thread t1(task1);
std::thread t2(task2);
t1.join();
t2.join();
MULTITHREADING 16
✅ std::lock() avoids deadlock by acquiring both mutexes
atomically.
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int main() {
std::thread t1(safePrint, "Hello");
std::thread t2(safePrint, "World");
t1.join();
t2.join();
return 0;
}
Benefits of std::unique_lock :
Supports std::condition_variable .
MULTITHREADING 17
✅condition_variable
1. Implement Producer-Consumer using mutex and
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
std::queue<int> buffer;
const unsigned int maxSize = 5;
std::mutex mtx;
std::condition_variable cv;
void producer() {
for (int i = 1; i <= 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return buffer.size() < maxSize; });
buffer.push(i);
std::cout << "Produced: " << i << std::endl;
cv.notify_all();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer() {
for (int i = 1; i <= 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !buffer.empty(); });
MULTITHREADING 18
cv.notify_all();
std::this_thread::sleep_for(std::chrono::milliseconds(150));
}
}
int main() {
std::thread prod(producer);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}
✅system)
2. Wait for a thread to signal before continuing (simple notifier
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void worker() {
std::cout << "Worker waiting...\n";
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; });
std::cout << "Worker proceeding after signal!\n";
}
int main() {
MULTITHREADING 19
std::thread t(worker);
std::this_thread::sleep_for(std::chrono::seconds(2));
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
}
cv.notify_one();
t.join();
return 0;
}
class BoundedBuffer {
private:
std::queue<int> buffer;
std::mutex mtx;
std::condition_variable not_full, not_empty;
size_t maxSize;
public:
BoundedBuffer(size_t size) : maxSize(size) {}
MULTITHREADING 20
buffer.push(value);
std::cout << "Produced: " << value << std::endl;
not_empty.notify_one();
}
int consume() {
std::unique_lock<std::mutex> lock(mtx);
not_empty.wait(lock, [this]() { return !buffer.empty(); });
not_full.notify_one();
return value;
}
};
int main() {
BoundedBuffer bb(5);
MULTITHREADING 21
std::thread prod(producer, std::ref(bb));
std::thread cons(consumer, std::ref(bb));
prod.join();
cons.join();
return 0;
}
std::queue<std::function<void()>> jobQueue;
std::mutex mtx;
std::condition_variable cv;
bool done = false;
void workerThread() {
while (true) {
std::function<void()> job;
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return !jobQueue.empty() || done; });
job = jobQueue.front();
jobQueue.pop();
MULTITHREADING 22
}
job();
}
}
int main() {
std::thread worker(workerThread);
{
std::lock_guard<std::mutex> lock(mtx);
done = true;
}
cv.notify_all();
worker.join();
return 0;
}
MULTITHREADING 23
Let me know when you're ready for the next set (e.g., thread pool, std::async , future ,
atomic ops, etc.).
Here are the revised solutions for the problems 1114. Print in Order, 1115. Print
FooBar Alternately, 1116. Print Zero Even Odd, and 1195. Fizz Buzz Multithreaded
without using lambda functions.
Solution:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class Foo {
private:
mutex mtx;
condition_variable cv;
int step = 0;
public:
Foo() {}
// Print "first"
void first() {
MULTITHREADING 24
unique_lock<mutex> lock(mtx);
cout << "first ";
step = 1;
cv.notify_all();
}
// Print "second"
void second() {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this] { return step == 1; });
cout << "second ";
step = 2;
cv.notify_all();
}
// Print "third"
void third() {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this] { return step == 2; });
cout << "third";
}
};
int main() {
Foo foo;
t1.join();
t2.join();
t3.join();
MULTITHREADING 25
return 0;
}
Output:
Solution:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class FooBar {
private:
int n;
mutex mtx;
condition_variable cv;
int turn = 0; // 0 for foo, 1 for bar
public:
FooBar(int n) {
this->n = n;
}
void foo() {
for (int i = 0; i < n; i++) {
unique_lock<mutex> lock(mtx);
MULTITHREADING 26
cv.wait(lock, [this] { return turn == 0; });
cout << "foo ";
turn = 1;
cv.notify_all();
}
}
void bar() {
for (int i = 0; i < n; i++) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this] { return turn == 1; });
cout << "bar ";
turn = 0;
cv.notify_all();
}
}
};
int main() {
int n = 5;
FooBar fooBar(n);
t1.join();
t2.join();
return 0;
}
Output:
foo bar foo bar foo bar foo bar foo bar
MULTITHREADING 27
### Problem 3: *1116. Print Zero Even Odd
✅ Fixed Version:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class ZeroEvenOdd {
private:
int n;
mutex mtx;
condition_variable cv;
int turn = 0; // 0 = zero, 1 = odd, 2 = even
int curr = 1;
public:
ZeroEvenOdd(int n) {
this->n = n;
}
void zero() {
for (int i = 1; i <= n; ++i) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this]() { return turn == 0; });
cout << "0 ";
if (i % 2 == 1) turn = 1;
else turn = 2;
cv.notify_all();
}
}
void odd() {
MULTITHREADING 28
for (int i = 1; i <= n; i += 2) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this]() { return turn == 1; });
cout << i << " ";
turn = 0;
cv.notify_all();
}
}
void even() {
for (int i = 2; i <= n; i += 2) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this]() { return turn == 2; });
cout << i << " ";
turn = 0;
cv.notify_all();
}
}
};
int main() {
int n = 5;
ZeroEvenOdd zeroEvenOdd(n);
t1.join();
t2.join();
t3.join();
return 0;
}
MULTITHREADING 29
✅ Correct Output:
0102030405
This ensures:
in a synchronized manner.
Solution:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
class FizzBuzz {
private:
int n;
mutex mtx;
condition_variable cv;
int counter = 1;
public:
FizzBuzz(int n) {
this->n = n;
}
MULTITHREADING 30
void fizz() {
for (int i = 1; i <= n; i++) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this] { return counter <= n && counter % 3 == 0 && count
er % 5 != 0; });
if (counter <= n) {
cout << "Fizz ";
counter++;
}
cv.notify_all();
}
}
void buzz() {
for (int i = 1; i <= n; i++) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this] { return counter <= n && counter % 5 == 0 && count
er % 3 != 0; });
if (counter <= n) {
cout << "Buzz ";
counter++;
}
cv.notify_all();
}
}
void fizzbuzz() {
for (int i = 1; i <= n; i++) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this] { return counter <= n && counter % 3 == 0 && count
er % 5 == 0; });
if (counter <= n) {
cout << "FizzBuzz ";
counter++;
}
cv.notify_all();
MULTITHREADING 31
}
}
void number() {
for (int i = 1; i <= n; i++) {
unique_lock<mutex> lock(mtx);
cv.wait(lock, [this] { return counter <= n && counter % 3 != 0 && count
er % 5 != 0; });
if (counter <= n) {
cout << counter << " ";
counter++;
}
cv.notify_all();
}
}
};
int main() {
int n = 15;
FizzBuzz fizzBuzz(n);
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}
Output:
MULTITHREADING 32
1 2 Fizz 4 Buzz Fizz 7 8 FizzBuzz 11 Fizz 13 14 FizzBuzz
Summary:
The solutions above use condition_variable and mutex for synchronization.
No lambda functions are used in these solutions; instead, the functions fizz() ,
buzz() , etc., are passed directly to the thread constructor.
Each solution ensures that the threads print in the correct order while
maintaining thread safety.
MULTITHREADING 33