Open In App

std::unique_lock or std::lock_guard: Which Is Better?

Last Updated : 28 May, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

In C++, to manage access to shared resources, the STL (standard template library) of C++, provides synchronization mechanisms such as std::lock_guard and std::unique_lock. Both are useful for managing mutex but have different features and use cases.

In this article, we will discuss the advantages, disadvantages, differences, and use cases of both std::unique_lock and std::lock_guard in C++

std::unique_lock

The std::unique_lock class provides more flexibility as compared to std::lock_guard. It provides features like manual locking, unlocking, deferred locking, transfer of ownership, and many more to the users. std::unique_lock requires explicit locking and unlocking, it doesn't automatically acquire and release lock like std::lock_guard. Following is the syntax to use std::unique_lock in C++:

Syntax

std::unique_lock<std::mutex> lock(mutex);
// Mutex is now locked

// Write the Code for Critical section

// Optional: manually unlock before the lock object goes out of scope
lock.unlock();

// Optional: re-lock if needed
lock.lock();

where lock() and unlock() are methods to acquire and release the mutex.

C++ Program using std::unique_lock

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

C++
// C++ Program using std::unique_lock
#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

Time Complexity: O(1)
Auxiliary Space: O(1)

Key Features

Following are some key features of std::unique_lock:

  • Flexibility: Can lock and unlock multiple times within its scope.
  • Deferred Locking: This can be constructed without locking the mutex immediately.
  • Timed Locking: Supports times and try-locking operations.
  • Ownership Transfer: It allows transferring mutex ownership to another std::unique_lock.

Usage

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

  • You need more control over the locking mechanism including ability to lock and unlock manually.
  • You need to defer locking or conditionally lock a mutex.
  • You require timed locking to prevent blocking indefinitely.
  • You need to transfer lock ownership between different scopes or threads.

std::lock_guard

std::lock_guard is a simple RAII-style (Resource Acquisition Is Initialization) mutex wrapper that locks a mutex on creation and automatically unlocks it upon destruction. It provides a straightforward way to ensure that a mutex is properly released even if an exception is thrown. Following is the syntax to use std::lock_guard in C++:

Syntax

std::lock_guard<std::mutex> guard(mutex);

// Mutex is now locked

// Critical section

// Mutex is automatically unlocked when 'guard' goes out of scope

C++ Program using std::lock_guard

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

C++
// C++ Program using std::lock_guard
#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

Time Complexity: O(1)
Auxiliary Space: O(1)

Key Features

Following are some key features of std::unique_lock:

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

Usage

Following are the use cases when you should consider using std::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.

Difference between std::lock_guard and std::unique_lock

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

Features

std::lock_guard

std::unique_lock

Complexity

Simple and minimal overhead.

It is more complex and have additional overhead due to extra features.

Locking behavior

Locks mutex upon construction and destruction.

Can lock and unlock multiple times within its scope and supports deferred locking.

Flexibility

No manual control over locking and unlocking.

There is manual control over locking and unlocking.

Deferred Locking

Not supported.

Supported.

Timed Locking

Not supported.

Supported.

Ownership Transfer

Not supported.

Supported.

Ideal Usage

Used where simple and single-scope locking is needed.

Used in complex scenarios needing lock control and flexibility.

Exception Safety

Guarantees unlock on destruction

It guarantees unlock on destruction more control may increase error risk if misused

Overhead

Minimal

Slightly higher due to additional features.

Example Use Case

Simple critical sections.

Conditional locking, timed locking and transferring lock ownership.

Conclusion

In conclusion, both std::unique_lock and std::lock_guard are powerful tools provided by the C++ standard library for managing mutexes and ensuring thread safety. std::lock_guard offers a simple, efficient way to lock and unlock a mutex within a scope, making it ideal for straightforward locking needs.Choosing between them depends on the specific requirements of your code.


Next Article
Practice Tags :

Similar Reads