std::scoped_lock
Определено в заголовке <mutex> | ||
|---|---|---|
template< class... MutexTypes > class scoped_lock; | (с C++17) |
Класс scoped_lock — это обёртка над мьютексом, предоставляющая удобный механизм RAII для владения нулем или более мьютексов на протяжении действия области видимости.
Когда создаётся объект scoped_lock, он пытается получить владение мьютексами, которые ему переданы. Когда управление выходит за пределы области видимости, в которой был создан объект scoped_lock, происходит уничтожение объекта scoped_lock, и мьютексы освобождаются. Если передаётся несколько мьютексов, используется алгоритм предотвращения тупиков, как если бы это делал std::lock.
Класс scoped_lock не допускает копирования.
Параметры шаблона
| MutexTypes | - | типы мьютексов для блокировки. Типы должны соответствовать требованиям Lockable, если sizeof...(MutexTypes)==1, в противном случае единственный тип должен соответствовать требованиям BasicLockable |
Типы-члены
| Тип-член | Определение |
|---|---|
mutex_type (если sizeof...(MutexTypes)==1) | Mutex, единственный тип в MutexTypes... |
Члены-функции
создаёт scoped_lock, при необходимости блокируя заданные мьютексы (публичный член-функция) |
|
уничтожает объект scoped_lock, разблокирует основанные мьютексы (публичный член-функция) |
|
| operator=
[удалено] | не допускает присваивания по значению (публичный член-функция) |
Примечания
Распространённая ошибка начинающих — «забыть» присвоить имя переменной scoped_lock, например, std::scoped_lock(mtx); (которая по умолчанию создаёт переменную scoped_lock с именем mtx) или std::scoped_lock{mtx}; (которая создаёт объект prvalue, который немедленно уничтожается), тем самым фактически не создавая блокировки, удерживающей мьютекс на оставшуюся часть области видимости.
| Макрокоманда проверки наличия функции | Значение | Стандарт | Функция |
|---|---|---|---|
__cpp_lib_scoped_lock | 201703L | (C++17) |
std::scoped_lock |
Пример
В следующем примере std::scoped_lock используется для блокировки пар мьютексов без тупиков и в стиле RAII.
#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';
}Возможный вывод:
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
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применено к | Поведение, как опубликовано | Правильное поведение |
|---|---|---|---|
| LWG 2981 | C++17 | избыточный руководящий принцип вывода от scoped_lock<MutexTypes...> был предоставлен | удалено |
См. также
|
(C++11) | реализует перемещаемую обёртку для мьютекса (шаблон класса) |
|
(C++11) | реализует обёртку для мьютекса, основанную исключительно на области видимости (шаблон класса) |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/thread/scoped_lock