unique_lock or lock_guard: Which Is Better?

Last Updated : 9 Jul, 2026

Both std::unique_lock and std::lock_guard are mutex wrappers provided by the C++ Standard Library to synchronize access to shared resources. While both automatically release the mutex when they go out of scope, they differ in flexibility, performance, and use cases.

  • Both provide RAII-based mutex management.
  • std::lock_guard is lightweight, whereas std::unique_lock offers additional control.

std::unique_lock

std::unique_lock is a flexible mutex wrapper that supports manual locking and unlocking, deferred locking, timed locking, and ownership transfer. It is suitable for scenarios where greater control over mutex management is required. Following are some key features of unique_lock:

  • Flexibility: Can lock and unlock multiple times within its scope.
  • Timed Locking: Supports times and try-locking operations.
  • Ownership Transfer: It allows transferring mutex ownership to another unique_lock.

Syntax

std::unique_lock<std::mutex> lock(mtx);
or
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);

Where:

  • lock is the unique_lock object.
  • mtx is the mutex object.
  • std::defer_lock creates the object without immediately locking the mutex.

Example: The following program illustrates the use of unique_lock in C++

C++
#include <mutex>
#include <thread>
#include <iostream>
using namespace std;

// Global mutex to protect shared_data
mutex mtx;

// Shared data variable
int shared_data = 0;

// Function to increment shared_data
void increment_data() {
    // Create a unique_lock object, but defer locking the mutex
    unique_lock<mutex> lock(mtx, defer_lock); 
    
    // Explicitly acquire the lock
    lock.lock(); 
    
    // Critical section: safely modify shared_data
    shared_data += 2;
    
    // Manually release the lock
    lock.unlock(); 
}

int main() {
    // Create two threads that run the increment_data function
    thread t1(increment_data);
    thread t2(increment_data);
    
    // Wait for both threads to finish
    t1.join();
    t2.join();
    
    // Output the value of shared_data
    cout << "Value of shared variable: " << shared_data;
    return 0;
}

Output
Value of shared variable: 4

Explanation

  • A unique_lock object is created with std::defer_lock and the mutex is explicitly locked using lock().
  • The shared variable is updated inside the critical section, and the mutex is manually unlocked before the object goes out of scope.

Common Use Cases

Following are the use cases when you should consider using unique_lock:

  • We need to defer locking or conditionally lock a mutex.
  • We require timed locking to prevent blocking indefinitely.
  • We need to transfer lock ownership between different scopes or threads.

std::lock_guard

std::lock_guard is a simple mutex wrapper that locks a mutex during construction and automatically unlocks it when the object goes out of scope. It is designed for straightforward synchronization with minimal overhead. Following are some key features of unique_lock:

  • Simplicity: lock_guard is very simple to use with minimal overhead.
  • RAII(Resource Acquisition Is Initialization) : Ensures that mutex is released when the lock_guard goes out of scope.
  • No Unlocking: Does not support manual unlocking before the end of its scope.

Syntax

std::lock_guard<std::mutex> lock(mtx);

Where:

  • lock is the lock_guard object.
  • mtx is the mutex object.

Example: The following program illustrates the use of lock_guard in C++

C++
#include <mutex>
#include <thread>
#include<iostream>
using namespace std;

// Global mutex to protect shared_data
mutex mtx;

// Shared data variable
int shared_data = 0;

// Function to increment shared_data
void increment_data() {
    // Create a lock_guard object which locks the mutex
    lock_guard<mutex> lock(mtx);
    
    // Critical section: safely modify shared_data
    shared_data+=2;
    
    // Lock is automatically released when 'lock' goes out of scope
}

int main() {
    // Create two threads that run the increment_data function
    thread t1(increment_data);
    thread t2(increment_data);
    
    // Wait for both threads to finish
    t1.join();
    t2.join();
    
    // Output the value of shared_data
    cout << "Value of shared variable: " << shared_data;
    
    return 0;
}

Output
Value of shared variable: 4

Common Use Cases

Following are the use cases when you should consider using lock_guard:

  • It is used when you need simple lock that automatically unlocks when the scope ends.
  • The locking operation is straightforward and does not require unlocking before scope ends.
  • You prioritize minimal overhead and simplicity.

std::unique_lock Vs std::lock_guard

Following are some key differences between lock_guard and unique_lock in C++:

Featurestd::lock_guardstd::unique_lock
Locking behaviorLocks immediately and unlocks automatically at scope exitSupports manual, deferred, and timed locking
Manual lock/unlockNot supportedSupported
Deferred lockingNot supportedSupported
Timed lockingNot supportedSupported
Ownership transferNot supportedSupported
std::condition_variable supportCannot be used directlyRequired by std::condition_variable
PerformanceLower overheadSlightly higher overhead
Best suited forSimple critical sectionsAdvanced synchronization scenarios
Comment