std::scoped_lock(3)
π NAME π
okay so bestie, welcome to std::scoped_lock(3) from the C++ Standard Library β the official NAME line is literally std::scoped_lock - std::scoped_lock, and honestly? iconic. no notes. π
πΈ Synopsis πΈ
Defined in header <mutex>:
template< class... MutexTypes > (since C++17)
class scoped_lock;
The class scoped_lock is a mutex wrapper that provides a convenient RAII-style mechanism for owning zero or more mutexes for the duration of a scoped block. itβs giving βlocks that clean up after themselvesβ β¨.
When a scoped_lock object is created, it attempts to take ownership of the mutexes it is given. When control leaves the scope in which the scoped_lock object was created, the scoped_lock is destructed and the mutexes are released. If several mutexes are given, a deadlock avoidance algorithm is used as if by std::lock. The scoped_lock class is non-copyable.
π¦ Template parameters π¦
- MutexTypes β the types of the mutexes to lock. The types must meet the
Lockablerequirements unlesssizeof...(MutexTypes)==1, in which case the only type must meetBasicLockable.
π Member types π
mutex_type(ifsizeof...(MutexTypes)==1) βMutex, the sole type inMutexTypes....
π Member functions π
- constructor β constructs a
scoped_lock, optionally locking the given mutexes (public member function) - destructor β destructs the
scoped_lockobject, unlocks the underlying mutexes (public member function) operator=β not copy-assignable[deleted](public member function)
π§ Notes π§
A common beginner error is to βforgetβ to give a scoped_lock variable a name, e.g. std::scoped_lock(mtx); (which default constructs a scoped_lock variable named mtx) or std::scoped_lock{mtx}; (which constructs a prvalue object that is immediately destroyed), thereby not actually constructing a lock that holds a mutex for the rest of the scope. bestie please name your locks, I am begging π.
Feature-test macro time:
__cpp_lib_scoped_lockβ value201703L, std(C++17), featurestd::scoped_lock.
π Example π
The following example uses std::scoped_lock to lock pairs of mutexes without deadlock and is RAII-style.
// Run this code
#include <chrono>
#include <functional>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
using namespace std::chrono_literals;
struct Employee
{
std::vector<std::string> lunch_partners;
std::string id;
std::mutex m;
Employee(std::string id) : id(id) {}
std::string partners() const
{
std::string ret = "Employee " + id + " has lunch partners: ";
for (int count{}; const auto& partner : lunch_partners)
ret += (count++ ? ", " : "") + partner;
return ret;
}
};
void send_mail(Employee&, Employee&)
{
// Simulate a time-consuming messaging operation
std::this_thread::sleep_for(1s);
}
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;
}
{
// Use std::scoped_lock to acquire two locks without worrying about
// other calls to assign_lunch_partner deadlocking us
// and it also provides a convenient RAII-style mechanism
std::scoped_lock lock(e1.m, e2.m);
// Equivalent code 1 (using std::lock and std::lock_guard)
// std::lock(e1.m, e2.m);
// std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);
// std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
// Equivalent code 2 (if unique_locks are needed, e.g. for condition variables)
// std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock);
// std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
// std::lock(lk1, lk2);
{
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);
}
send_mail(e1, e2);
send_mail(e2, e1);
}
int main()
{
Employee alice("Alice"), bob("Bob"), christina("Christina"), dave("Dave");
// Assign in parallel threads because mailing users about lunch assignments
// takes a long time
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.partners() << '\n' << bob.partners() << '\n'
<< christina.partners() << '\n' << dave.partners() << '\n';
}
β¨ Possible output β¨
Alice and Bob are waiting for locks
Alice and Bob got locks
Christina and Bob are waiting for locks
Christina and Alice are waiting for locks
Dave and Bob are waiting for locks
Dave and Bob got locks
Christina and Alice got locks
Christina and Bob got locks
Employee Alice has lunch partners: Bob, Christina
Employee Bob has lunch partners: Alice, Dave, Christina
Employee Christina has lunch partners: Alice, Bob
Employee Dave has lunch partners: Bob
π©Ή Defect reports π©Ή
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
- DR β
LWG 2981 - Applied to β C++17
- Behavior as published β redundant deduction guide from
scoped_lock<MutexTypes...>was provided - Correct behavior β removed
π See also π
unique_lockβ implements movable mutex ownership wrapper(C++11)(class template)lock_guardβ implements a strictly scope-based mutex ownership wrapper(C++11)(class template)
xoxo, std::scoped_lock(3) β source/version: http://cppreference.com, date: 2024.06.10 πβ¨
π¦ where this page comes from
- nixpkgs unstable: stdman 2024.07.05
- opensuse-leap 16.0: stdman 2024.07.05-bp160.1.9 Β· MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms