This complete tutorial on Multithreading and Concurrency in C++ explains creating threads, synchronization with mutexes and locks, condition variables, and atomic operations. It helps learners write thread-safe and efficient concurrent C++ programs following modern best practices.
Multithreading and Concurrency – Complete Tutorial
1. Threads
Threads allow multiple tasks to run concurrently within a program.
Creating Threads
#include <iostream>
#include <thread>
using namespace std;
void task(int n) {
cout << "Thread task " << n << endl;
}
int main() {
thread t1(task, 1);
thread t2(task, 2);
t1.join(); // wait for thread to finish
t2.join();
}
Notes:
join()waits for a thread to completedetach()runs a thread independently
2. Mutex
Mutex (mutual exclusion) ensures only one thread accesses a resource at a time.
Example:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
mutex mtx;
void printNumbers(int id) {
mtx.lock();
for(int i=1;i<=5;i++) cout << "Thread " << id << ": " << i << endl;
mtx.unlock();
}
int main() {
thread t1(printNumbers,1);
thread t2(printNumbers,2);
t1.join(); t2.join();
}
Best Practice: Use std::lock_guard for RAII-based automatic unlocking
3. Locks
Locks simplify mutex management.
Example – lock_guard:
void printNumbers(int id) {
lock_guard<mutex> lock(mtx); // automatically locks and unlocks
for(int i=1;i<=5;i++) cout << "Thread " << id << ": " << i << endl;
}
Types of Locks:
lock_guard– automatic, simpleunique_lock– flexible, supportsdefer_lock,try_lock
4. Condition Variables
Condition variables allow threads to wait for certain conditions before proceeding.
Example:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
mutex mtx;
condition_variable cv;
bool ready = false;
void worker() {
unique_lock<mutex> lock(mtx);
cv.wait(lock, []{ return ready; }); // wait until ready is true
cout << "Worker thread running\n";
}
int main() {
thread t(worker);
this_thread::sleep_for(chrono::seconds(1));
{
lock_guard<mutex> lock(mtx);
ready = true;
}
cv.notify_one();
t.join();
}
Use Cases: producer-consumer, task synchronization
5. Atomic Operations
Atomic operations allow safe concurrent updates without locks.
Example:
#include <iostream>
#include <thread>
#include <atomic>
using namespace std;
atomic<int> counter(0);
void increment() {
for(int i=0;i<1000;i++) counter++;
}
int main() {
thread t1(increment), t2(increment);
t1.join(); t2.join();
cout << "Counter: " << counter << endl; // always 2000
}
Notes:
- Avoids race conditions
- Faster than mutex for simple operations
Best Practices
- Use RAII locks (
lock_guard) to avoid deadlocks - Prefer atomic operations for simple counters
- Minimize shared resources between threads
- Use condition variables for proper synchronization
- Always join or detach threads to prevent undefined behavior
Common Mistakes
- Forgetting to unlock mutex manually
- Using
detachwithout managing thread lifetime - Not protecting shared resources (race conditions)
- Overusing threads causing context-switch overhead
Summary
In this chapter, you learned about Multithreading and Concurrency in C++, including:
- Creating and managing threads
- Synchronizing with mutexes and locks
- Waiting with condition variables
- Performing safe atomic operations
Mastering multithreading allows writing high-performance, parallel, and thread-safe C++ programs.