Spec-Zone.ru › C++

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...> был предоставлен удалено

См. также

unique_lock
(C++11)
реализует перемещаемую обёртку для мьютекса
(шаблон класса)
lock_guard
(C++11)
реализует обёртку для мьютекса, основанную исключительно на области видимости
(шаблон класса)

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/thread/scoped_lock

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API