Mutex (Mutual Exclusion)
Mutex is a synchronization object that controls access to shared resources in a multithreaded environment.
It is used to prevent Race Conditions that can occur when multiple threads access the same resource simultaneously.
Code Example
#include <mutex>
#include <thread>
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
mtx.lock();
counter++; // Critical Section
mtx.unlock();
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl;
return 0;
}
Key Methods
-
lock()- Locks the mutex in a blocking manner.
- If another thread has already acquired the lock, it waits until it is released.
-
unlock()- Releases the acquired lock.
- Calling this from a thread that hasn't acquired the lock results in undefined behavior.
-
try_lock()- Attempts to lock in a non-blocking manner, returning true on success and false immediately on failure.
- Allows performing other tasks without waiting to acquire the lock.
As shown in the example code above, the code region between lock() and unlock() (Critical Section) can only be executed by one thread at a time.
However, if an exception occurs or an early return happens before calling unlock(), a deadlock will occur.
Mutex for Special Situations
-
recursive_mutex- A mutex that allows the same thread to acquire the lock multiple times.
- Must call
unlock()as many times as the lock was acquired to fully release it.
-
timed_mutex- A mutex that allows specifying a timeout.
- Provides
try_lock_for()andtry_lock_until()methods.
std::timed_mutex tmtx;
void function() {
if (tmtx.try_lock_for(std::chrono::seconds(1))) {
// Successfully acquired lock within 1 second
// Critical Section
tmtx.unlock();
} else {
// Timeout occurred
}
}
-
shared_mutex- Implements a Reader-Writer Lock.
- Supported from C++17.
- Multiple threads can perform read operations simultaneously, but write operations are performed exclusively.
Lock Guard
Manually managing lock() and unlock() is dangerous. The solution to this problem is the RAII (Resource Acquisition Is Initialization) pattern. It acquires resources in the constructor and releases them in the destructor, utilizing C++'s stack unwinding mechanism to ensure resources are safely released even when exceptions occur.
lock guard is one of the classes provided by the C++ standard library that helps reduce mistakes in mutex management.
When using lock guard, the mutex is automatically locked, and when the scope is exited, the lock_guard's destructor is called to automatically release the mutex.
lock_guard
- The most basic RAII-based mutex wrapper.
- Automatically calls lock() on construction and unlock() on destruction.
- The simplest with minimal overhead.
- Acquires the lock immediately upon creation.
- Cannot control when the lock is released.
- Cannot be copied or moved.
#include <mutex>
#include <thread>
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // Lock on creation
counter++;
// Automatically unlocks when leaving scope
}
}
void safe_function() {
std::lock_guard<std::mutex> lock(mtx);
if (some_condition)
return;
process_data(); // Unlock guaranteed even if exception occurs
}
unique_lock
- Provides various features including deferred locking, condition variable integration, and ownership transfer.
- Can manually call
lock()/unlock(). - Essential for use with condition variables like
condition_variable. - Movable but not copyable.
std::mutex mtx;
void flexible_function() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // Deferred locking
prepare_data();
lock.lock(); // Manually lock at the needed point
modify_shared_data();
lock.unlock(); // Can manually release
cleanup(); // Other tasks without lock
// Automatically unlocks if still locked when scope ends
}
// Use with condition variables (most common case)
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void wait_for_signal() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // unique_lock required
process_data();
}
scoped_lock
- Supported from C++17.
- Used to prevent deadlocks when locking multiple mutexes simultaneously.
- Uses the
std::lock()algorithm internally. - Identical to
lock_guardfor a single mutex.
std::mutex mtx1, mtx2;
// Code that can cause deadlock
void thread1() {
std::lock_guard<std::mutex> lock1(mtx1);
std::lock_guard<std::mutex> lock2(mtx2); // Order issue
// ...
}
void thread2() {
std::lock_guard<std::mutex> lock2(mtx2);
std::lock_guard<std::mutex> lock1(mtx1); // Opposite order!
// ...
}
// Prevent deadlock with scoped_lock
void safe_thread1() {
std::scoped_lock lock(mtx1, mtx2); // Uses deadlock avoidance algorithm
// ...
}
void safe_thread2() {
std::scoped_lock lock(mtx2, mtx1); // Safe regardless of order
// ...
}
Lock Guard is a method for safely managing mutexes using the RAII pattern.
Since locks are automatically released even in exception or early return situations, it is much safer than manually managing lock()/unlock().
In most cases, lock_guard is sufficient, and unique_lock or scoped_lock should only be used in special situations.
Proper use of these can prevent many bugs that can occur in multithreaded programming.
Top comments (0)