Multidimensional Arrays in C++
Multithreading lets your program do more than one thing at the same time. Each independent task runs in a thread — a unit of execution managed by the operating system. Multiple threads inside the same program share memory and run concurrently, making programs faster on multi-core processors.
Think of threads like workers in a kitchen. A single cook (one thread) prepares everything one step at a time. Multiple cooks (multiple threads) work on different dishes simultaneously, finishing the meal much faster. C++11 introduced the <thread> library to support multithreading directly.
Single Thread vs Multi-Thread
Single-threaded:
Task A → Task B → Task C (sequential, takes longer)
Multi-threaded:
Thread 1: Task A ─────────────────────→ done
Thread 2: Task B ─────────────→ done
Thread 3: Task C ──────────────────────────→ done
All run at the same time — total time = longest task
Creating a Thread
Include <thread> and create a std::thread object, passing it the function the thread should run.
#include <iostream>
#include <thread>
using namespace std;
void greet(string name) {
cout << "Hello from thread: " << name << endl;
}
int main() {
thread t1(greet, "Alice");
thread t2(greet, "Bob");
t1.join(); // wait for t1 to finish
t2.join(); // wait for t2 to finish
cout << "Both threads completed." << endl;
return 0;
}
Sample Output (order may vary):
Hello from thread: Alice
Hello from thread: Bob
Both threads completed.
The order of "Alice" and "Bob" may differ each run because both threads run concurrently and the OS decides which one gets CPU time first.
join() and detach()
After creating a thread, you must decide what to do with it before the program ends.
| Method | Meaning |
|---|---|
t.join() | Main thread waits here until thread t finishes |
t.detach() | Thread t runs independently; main does not wait for it |
t.joinable() | Returns true if the thread can still be joined or detached |
If you neither join nor detach a thread before the program exits, the program terminates abnormally (std::terminate is called).
Threads with Lambdas
#include <iostream>
#include <thread>
using namespace std;
int main() {
thread t([]() {
for (int i = 1; i <= 5; i++) {
cout << "Worker: " << i << endl;
}
});
for (int i = 1; i <= 5; i++) {
cout << "Main: " << i << endl;
}
t.join();
return 0;
}
Sample Output (interleaved, order varies):
Main: 1
Worker: 1
Main: 2
Worker: 2
...
The Race Condition Problem
When multiple threads read and write the same variable at the same time without coordination, the result becomes unpredictable. This is called a race condition.
#include <iostream>
#include <thread>
using namespace std;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; i++) {
counter++; // NOT thread-safe!
}
}
int main() {
thread t1(increment);
thread t2(increment);
t1.join();
t2.join();
cout << "Counter: " << counter << endl; // expected 200000, but result varies
return 0;
}
Sample Output:
Counter: 174382 (wrong! race condition)
Why it happens:
Thread 1 reads counter = 50
Thread 2 reads counter = 50 ← both read the same value
Thread 1 writes counter = 51
Thread 2 writes counter = 51 ← one increment is lost!
Fixing Race Conditions with mutex
A mutex (mutual exclusion) is a lock. Only one thread can hold the lock at a time. Other threads wait until the lock is released. This ensures that only one thread modifies shared data at a time.
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int counter = 0;
mutex mtx;
void safeIncrement() {
for (int i = 0; i < 100000; i++) {
mtx.lock(); // acquire the lock
counter++; // only one thread runs this at a time
mtx.unlock(); // release the lock
}
}
int main() {
thread t1(safeIncrement);
thread t2(safeIncrement);
t1.join();
t2.join();
cout << "Counter: " << counter << endl; // always 200000
return 0;
}
Output:
Counter: 200000
lock_guard — Safer Mutex Handling
Calling lock() and unlock() manually is risky. If the code throws an exception between them, unlock() never runs and the program deadlocks. lock_guard locks the mutex automatically and unlocks it when it goes out of scope — even if an exception occurs.
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int counter = 0;
mutex mtx;
void safeIncrement() {
for (int i = 0; i < 100000; i++) {
lock_guard<mutex> guard(mtx); // auto-unlock when guard goes out of scope
counter++;
}
}
int main() {
thread t1(safeIncrement);
thread t2(safeIncrement);
t1.join();
t2.join();
cout << "Counter: " << counter << endl;
return 0;
}
Thread with Return Value — std::future and std::async
std::async runs a function asynchronously and returns a std::future object. You call .get() on the future to retrieve the result when the thread finishes.
#include <iostream>
#include <future>
using namespace std;
int computeSum(int a, int b) {
return a + b;
}
int main() {
future<int> result = async(computeSum, 30, 70);
cout << "Sum: " << result.get() << endl; // waits for result
return 0;
}
Output:
Sum: 100
Common Multithreading Issues
┌──────────────────┬─────────────────────────────────────────────────────┐
│ Problem │ Description │
├──────────────────┼─────────────────────────────────────────────────────┤
│ Race condition │ Two threads modify shared data simultaneously │
│ Deadlock │ Two threads each wait for a lock the other holds │
│ Starvation │ A thread never gets a chance to run │
│ Data corruption │ Unsynchronized writes leave data in invalid state │
└──────────────────┴─────────────────────────────────────────────────────┘
Key Takeaways
- Threads allow concurrent execution of code within the same program.
- Create threads with
std::thread; always calljoin()ordetach()before the thread object is destroyed. - Race conditions occur when multiple threads access shared data without synchronization.
- Use
std::mutexto protect shared data; preferlock_guardover manual lock/unlock. - Use
std::asyncandstd::futurewhen a thread needs to return a value. - Compile multithreading code with the
-lpthreadflag on Linux/GCC.
