What is the scoped locking idiom?

Overview

The scoped lock class is a mutex wrapper that makes owning one or more mutexes for the length of a scoped block easier.

Purpose

Scoped locks lock a mutex when they’re constructed and unlock it when they’re destructed. When the control flow departs a scope, the C++ rules ensure that objects local to the scope being vacated are properly destructed. This implies that utilizing a scoped lock rather than manually executing lock() and unlock() is a good approach. This makes it impossible to not unlock the mutex even accidentally. An example of this is when an exception is triggered between lock() and unlock().

Example

std::scoped_lock combines RAII-style semantics for owning one or more mutexes with the lock avoidance methods used by std::lock. It gives RAII-style semantics for owning one or more mutexes. Mutexes are released in the opposite order from which they were acquired when std::scoped lock is destroyed.

{
    std::scoped_lock lock{_mutex1,_mutex2};
    //do something
}
#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <functional>
#include <chrono>
#include <string>
struct Employee {
Employee(std::string id) : id(id) {}
std::string id;
std::vector<std::string> lunch_partners;
std::mutex m;
std::string output() const
{
std::string ret = "Employee " + id + " has lunch partners: ";
for( const auto& partner : lunch_partners )
ret += partner + " ";
return ret;
}
};
void assign_lunch_partner(Employee &e1, Employee &e2)
{
static std::mutex io_mutex;
{
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl;
}
{
std::scoped_lock lock(e1.m, e2.m);
{
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << e1.id << " and " << e2.id << " got locks" << std::endl;
}
e1.lunch_partners.push_back(e2.id);
e2.lunch_partners.push_back(e1.id);
}
}
int main()
{
Employee alice("alice"), bob("bob"), christina("christina"), dave("dave");
std::vector<std::thread> threads;
threads.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob));
threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob));
threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice));
threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob));
for (auto &thread : threads) thread.join();
std::cout << alice.output() << '\n' << bob.output() << '\n'
<< christina.output() << '\n' << dave.output() << '\n';
}

Explanation

  • Lines 9 to 21: We create a structure named Employee that returns an employee’s output and their assigned lunch partners.

  • Lines 23 to 41: We create a function named assign_lunch_partner where scoped_lock combines RAII-style semantics for owning two mutexes and locks them.

  • Lines 43 to 56: We create threads that combine the mutexes and assign lunch partners to an employee.

Copyright ©2024 Educative, Inc. All rights reserved