0
0
CppHow-ToBeginner · 4 min read

How to Use unique_lock in C++ for Flexible Mutex Locking

Use std::unique_lock to manage mutex locking in C++ with flexible lock control and automatic unlocking. It allows deferred locking, timed locking, and manual unlocking, making it more versatile than std::lock_guard. Create a unique_lock by passing a mutex, and it locks automatically unless specified otherwise.
📐

Syntax

The basic syntax to create a std::unique_lock is:

  • std::unique_lock lock(mutex); - locks the mutex immediately.
  • std::unique_lock lock(mutex, std::defer_lock); - creates the lock without locking the mutex.
  • lock.lock(); - manually locks the mutex later.
  • lock.unlock(); - manually unlocks the mutex before the lock object is destroyed.
cpp
std::mutex mtx;
std::unique_lock<std::mutex> lock(mtx); // locks immediately

std::unique_lock<std::mutex> lock2(mtx, std::defer_lock); // no lock yet
lock2.lock(); // lock later
lock2.unlock(); // unlock manually
💻

Example

This example shows how std::unique_lock locks a mutex to protect shared data and automatically unlocks it when the lock object goes out of scope.

cpp
#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int counter = 0;

void increment() {
    std::unique_lock<std::mutex> lock(mtx); // lock mutex
    ++counter;
    std::cout << "Counter: " << counter << " by thread " << std::this_thread::get_id() << std::endl;
    // mutex unlocks automatically when lock goes out of scope
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    return 0;
}
Output
Counter: 1 by thread 140735204489344 Counter: 2 by thread 140735196096640
⚠️

Common Pitfalls

Common mistakes when using std::unique_lock include:

  • Locking the same mutex twice without unlocking, causing deadlocks.
  • Using std::unique_lock without a mutex or with a null mutex.
  • Manually unlocking and then letting the destructor unlock again, which is safe but can confuse readers.
  • Not understanding that unique_lock can be moved but not copied.

Always ensure the mutex is valid and avoid double locking.

cpp
std::mutex mtx;

// Wrong: locking twice without unlock
std::unique_lock<std::mutex> lock(mtx);
// lock.lock(); // ERROR: double lock causes deadlock

// Correct way:
std::unique_lock<std::mutex> lock2(mtx, std::defer_lock);
lock2.lock(); // lock once
// ...
lock2.unlock(); // unlock before locking again if needed
📊

Quick Reference

std::unique_lock features:

  • Automatic locking and unlocking of mutex.
  • Supports deferred locking, timed locking, and manual unlocking.
  • Can be moved but not copied.
  • More flexible than std::lock_guard.
FeatureDescription
Automatic lockLocks mutex on construction by default
Deferred lockConstruct with std::defer_lock to delay locking
Manual lock/unlockCall lock() and unlock() methods explicitly
MoveableCan transfer ownership with move semantics
Not copyableCopy constructor and assignment are deleted

Key Takeaways

Use std::unique_lock for flexible and safe mutex management in C++.
It locks the mutex on creation by default but supports deferred locking.
Always avoid double locking the same mutex without unlocking.
unique_lock unlocks automatically when it goes out of scope.
It can be moved but not copied, enabling ownership transfer.