Multithreading and Concurrency in C++ | Threads, Mutex, Locks, Condition Variables, Atomic Operations


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:

  1. join() waits for a thread to complete
  2. detach() 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:

  1. lock_guard – automatic, simple
  2. unique_lock – flexible, supports defer_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:

  1. Avoids race conditions
  2. Faster than mutex for simple operations

Best Practices

  1. Use RAII locks (lock_guard) to avoid deadlocks
  2. Prefer atomic operations for simple counters
  3. Minimize shared resources between threads
  4. Use condition variables for proper synchronization
  5. Always join or detach threads to prevent undefined behavior

Common Mistakes

  1. Forgetting to unlock mutex manually
  2. Using detach without managing thread lifetime
  3. Not protecting shared resources (race conditions)
  4. Overusing threads causing context-switch overhead

Summary

In this chapter, you learned about Multithreading and Concurrency in C++, including:

  1. Creating and managing threads
  2. Synchronizing with mutexes and locks
  3. Waiting with condition variables
  4. Performing safe atomic operations

Mastering multithreading allows writing high-performance, parallel, and thread-safe C++ programs.