In this article, we will discuss the std::deferlockt, std::trytolockt, and std::adoptlockt in C++ with its syntax and example. These three tag types are available in C++, std::deferlockt, std::trytolockt, and std::adoptlockt. These tag types are mainly used in conjunction with std::uniquelock and std::lockguard to define the locking behavior when working with mutexes.
What is the Std::defer_lock_t?
Delaying locking while creating an object, such as std::uniquelock or std::lockguard is possible with this tag type. When a lock object is constructed using std::defer_lock, the mutex linked to the lock is not locked right away. After that, we can subsequently explicitly lock the mutex using the lock member function.
Syntax:
It has the following syntax:
std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex, std::defer_lock);
// Mutex is not locked yet
lock.lock(); // Now, the mutex is locked
What is the Std::try_to_lock_t?
The purpose of this tag type is to try and obtain the lock without obstructing. The lock constructor won't block and the lock object will be created in an unlocked state if the lock isn't available. It enables us to try to obtain the lock through non-blocking methods.
Syntax:
It has the following syntax:
std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex, std::try_to_lock);
if (lock.owns_lock()) {
// Lock was acquired successfully
} else {
// Lock was not acquired
}
What is the Std::adopt_lock_t?
This tag type is used to indicate that the calling thread already has ownership of the lock associated with the mutex. When we are moving a lock from one scope to another, we usually use it for this purpose.
Syntax:
It has the following syntax:
std::mutex mutex;
mutex.lock(); // Lock the mutex
std::unique_lock<std::mutex> lock(mutex, std::adopt_lock);
// Now, the lock is owned by the unique_lock object
Example 1:
Let us take an example to illustrate the std::deferlockt, std::trytolockt, and std::adoptlock_t in C++.
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
// Function to demonstrate locking behavior
void print_thread_id(std::unique_lock<std::mutex>& lock, const char* msg) {
lock.lock(); // Manually lock the mutex
std::cout << msg << " Thread ID: " << std::this_thread::get_id() << std::endl;
}
int main() {
// Example using std::defer_lock_t
{
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
print_thread_id(lock, "Deferred Locking:");
// Do some work
} // Lock is automatically released when lock goes out of scope
// Example using std::try_to_lock_t
{
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
print_thread_id(lock, "Try To Locking:");
// Do some work
} else {
std::cout << "Try To Locking: Lock was not acquired immediately." << std::endl;
}
}
// Example using std::adopt_lock_t
{
mtx.lock(); // Lock the mutex manually before creating the lock
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);
print_thread_id(lock, "Adopt Locking:");
// Do some work
} // Lock is not unlocked automatically because it was adopted
return 0;
}
Output:
Deferred Locking: Thread ID: 135034438377280
terminate called after throwing an instance of 'std::system_error'
what(): Resource deadlock avoided
Aborted
Explanation:
In this example, this code illustrates how to apply each tag type in various contexts. In the initial situation, locking is postponed until specifically asked for by std::deferlock. In the third scenario, std::adoptlock makes the assumption that the calling thread already has the lock, while in the other scenario, std::trytolock makes an attempt to obtain the lock without blocking.
Example 2:
Let us take another example to illustrate the std::deferlockt, std::trytolockt, and std::adoptlock_t in C++.
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
// Function to demonstrate locking behavior
void print_thread_id(const char* msg) {
std::lock_guard<std::mutex> lock(mtx); // Lock the mutex
std::cout << msg << " Thread ID: " << std::this_thread::get_id() << std::endl;
} // Lock is automatically released when lock goes out of scope
int main() {
// Example using std::defer_lock_t
{
std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
print_thread_id("Deferred Locking:");
// Do some work
lock.lock(); // Explicitly lock the mutex
}
// Example using std::try_to_lock_t
{
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
if (lock.owns_lock()) {
print_thread_id("Try To Locking:");
// Do some work
} else {
std::cout << "Try To Locking: Lock was not acquired immediately." << std::endl;
}
}
// Example using std::adopt_lock_t
{
mtx.lock(); // Lock the mutex manually
std::unique_lock<std::mutex> lock(mtx, std::adopt_lock);
print_thread_id("Adopt Locking:");
// Do some work
} // Lock is automatically released when unique_lock goes out of scope
return 0;
}
Output:
Deferred Locking: Thread ID: 138276743659328
Try To Locking: Thread ID: 138276743659328
Adopt Locking: Thread ID: 138276743659328
Conclusion:
In conclusion, essential flexibility in handling mutexes in concurrent programming is offered by the std::deferlockt, std::trytolockt, and std::adoptlockt tag types. Control over the locking sequence is made possible by std::deferlockt, which permits a mutex to be locked without explicit request. The program can handle scenarios where immediate lock acquisition is not possible by enabling non-blocking lock acquisition through the use of std::trytolockt, which makes an attempt to acquire the lock without blocking. Last but not least, std::adoptlockt implies that the calling thread already possesses the lock linked to the mutex. It is helpful in situations where a mutex is shared by several program components or where ownership of a lock needs to be transferred. Writing more expressive, effective, and reliable concurrent C++ code is made possible by these tag types, which are mainly utilized with std::uniquelock and std::lockguard.