← Back to C++ Course | Chapter 17: Advanced C++ | Lesson 13 of 17

C++ Mutex & Locks

Data Races and Mutex

A data race occurs when multiple threads try to write to the same shared memory location at the same time, leading to corrupted data. You can protect shared data using a 'std::mutex' to ensure only one thread can access it at a time.

Example: Data Races and Mutex

cpp
#include <iostream>
#include <mutex>

std::mutex m;
int counter = 0;

void increment() {
	m.lock();
	counter++; // protected from concurrent writes
	m.unlock();
}

int main() {
	increment();
	std::cout << counter << std::endl;
	return 0;
}

Lock Guard (std::lock_guard)

Using manual lock() and unlock() can be dangerous because if your code throws an exception or returns early, the mutex stays locked forever. Use 'std::lock_guard' to lock a mutex. It automatically unlocks the mutex when it goes out of scope.

Example: Lock Guard (std::lock_guard)

cpp
#include <iostream>
#include <mutex>

std::mutex m;
int counter = 0;

void increment() {
	std::lock_guard<std::mutex> lock(m); // auto-unlocks, even on early return/exception
	counter++;
}

int main() {
	increment();
	std::cout << counter << std::endl;
	return 0;
}

Unique Lock (std::unique_lock)

The 'std::unique_lock' class is a more flexible version of lock_guard. It allows you to manually lock and unlock the mutex at any point, deferred lock acquisitions, and manage time-delayed locks.

Example: Unique Lock (std::unique_lock)

cpp
#include <iostream>
#include <mutex>

std::mutex m;

int main() {
	std::unique_lock<std::mutex> lock(m); // can unlock/relock manually
	lock.unlock();
	lock.lock();
	std::cout << "Manually managed lock" << std::endl;
	return 0;
}

Deadlocks

A deadlock occurs when two threads are blocked forever, each waiting for a mutex held by the other. To avoid deadlocks, use C++17's 'std::scoped_lock' to lock multiple mutexes safely and simultaneously.

Example: Deadlocks

cpp
#include <iostream>
#include <mutex>

std::mutex m1, m2;

int main() {
	std::lock(m1, m2); // locks both without risking a deadlock, unlike locking separately
	std::lock_guard<std::mutex> lock1(m1, std::adopt_lock);
	std::lock_guard<std::mutex> lock2(m2, std::adopt_lock);
	std::cout << "Both locked safely" << std::endl;
	return 0;
}

Best Practices for Shared Memory

Keep your critical sections (the parts of your code that are locked) as small as possible. This ensures that threads spend less time waiting for locks, which improves program throughput.

Example: Best Practices for Shared Memory

cpp
#include <iostream>
#include <mutex>

std::mutex m;
int counter = 0;

void increment() {
	{
		std::lock_guard<std::mutex> lock(m); // critical section kept as small as possible
		counter++;
	}
	std::cout << "Non-critical work outside the lock" << std::endl;
}

int main() {
	increment();
	std::cout << counter << std::endl;
	return 0;
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.