C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов читать книгу онлайн
С++ — объектно-ориентированный язык программирования, без которого сегодня немыслима промышленная разработка ПО. В этой замечательной книге описана работа с контейнерами, алгоритмами, вспомогательными классами, лямбда-выражениями и другими интересными инструментами, которыми богат современный С++. Освоив материал, вы сможете коренным образом пересмотреть привычный подход к программированию.
Преимущество издания — в подробном описании стандартной библиотеки шаблонов С++, STL. Ее свежая версия была выпущена в 2017 году. В книге вы найдете более 90 максимально реалистичных примеров, которые демонстрируют всю мощь STL. Многие из них станут базовыми кирпичиками для решения более универсальных задач.
Вооружившись этой книгой, вы сможете эффективно использовать С++17 для создания высококачественного и высокопроизводительного ПО, применимого в различных отраслях.
В момент, когда функция main возвращает значение, приложение заканчивает работу. Однако в это же время наш открепленный поток t3 все еще находится в приостановленном состоянии и не успевает отправить сообщение bye на консоль. Операционной системе это неважно: она просто завершает всю программу, не дожидаясь завершения данного потока. Указанный факт важно иметь в виду. Если дополнительный поток должен был соревноваться за что-то важное, то нужно было бы подождать его завершения в функции main.
Выполняем устойчивую к исключениям общую блокировку с помощью td::unique_lock и std::shared_lock
Поскольку работа потоков значительно зависит от поддержки операционной системы, а STL предоставляет хорошие интерфейсы, позволяющие абстрагироваться от операционных систем, разумно также предоставить поддержку STL для синхронизации между потоками. Таким образом, можно не только запускать и останавливать потоки без внешних библиотек, но и синхронизировать их с помощью абстракций из одной объединенной библиотеки — STL.
В этом разделе мы взглянем на классы-мьютексы STL и абстракции блокировки RAII. Поэкспериментируем с ними в нашей конкретной реализации примера, а также изучим другие вспомогательные средства синхронизации, предоставляемые STL.
Как это делается
В этом примере мы напишем программу, которая использует экземпляр класса std::shared_mutex в эксклюзивном и коллективном режимах, и увидим, что это значит. Кроме того, не будем вызывать функции lock и unlock самостоятельно, а сделаем это с помощью вспомогательных функций RAII.
1. Сначала включим все необходимые заголовочные файлы. Поскольку мы задействуем функции и структуры данных STL, а также временные литералы, объявим об использовании пространств имен std и chrono_literal:
#include <iostream>
#include <shared_mutex>
#include <thread>
#include <vector>
using namespace std;
using namespace chrono_literals;
2. Вся программа строится вокруг одного общего мьютекса, поэтому для простоты объявим его глобальный экземпляр:
shared_mutex shared_mut;
3. Мы будем использовать вспомогательные функции RAII std::shared_lock и std::unique_lock. Чтобы их имена выглядели более понятными, определим для них короткие псевдонимы:
using shrd_lck = shared_lock<shared_mutex>;
using uniq_lck = unique_lock<shared_mutex>;
4. Прежде чем начнем писать функцию main, определим две вспомогательные функции, которые пытаются заблокировать мьютекс в эксклюзивном режиме. Эта функция создаст экземпляр класса unique_lock для общего мьютекса. Второй аргумент конструктора defer_lock указывает объекту поддерживать блокировку снятой. В противном случае его конструктор попробует заблокировать мьютекс, а затем будет удерживать его до завершения. Далее вызываем метод try_lock для объекта exclusive_lock. Этот вызов немедленно вернет булево значение, которое говорит, получили мы блокировку или же мьютекс уже был заблокирован кем-то еще.
static void print_exclusive()
{
uniq_lck l {shared_mut, defer_lock};
if (l.try_lock()) {
cout << "Got exclusive lock.n";
} else {
cout << "Unable to lock exclusively.n";
}
}
5. Другая вспомогательная функция также пытается заблокировать мьютекс в эксклюзивном режиме. Она делает это до тех пор, пока не получит блокировку. Затем мы симулируем какую-нибудь ошибку, генерируя исключение (содержащее лишь простое целое число). Несмотря на то, что это приводит к мгновенному выходу контекста, в котором мы хранили заблокированный мьютекс, последний будет освобожден. Это происходит потому, что деструктор объекта unique_lock освободит блокировку в любом случае по умолчанию.
static void exclusive_throw()
{
uniq_lck l {shared_mut};
throw 123;
}
6. Теперь перейдем к функции main. Сначала откроем еще одну область видимости и создадим экземпляр класса shared_lock. Его конструктор мгновенно заблокирует мьютекс в коллективном режиме. Мы увидим, что это значит, в следующих шагах.
int main()
{
{
shrd_lck sl1 {shared_mut};
cout << "shared lock once.n";
7. Откроем еще одну область видимости и создадим второй экземпляр типа shared_lock для того же мьютекса. Теперь у нас есть два экземпляра типа shared_lock, и оба содержат общую блокировку мьютекса. Фактически можно создать произвольно большое количество экземпляров типа shared_lock для одного мьютекса. Затем вызываем функцию print_exclusive, которая пытается заблокировать мьютекс в эксклюзивном режиме. Эта операция не увенчается успехом, поскольку он уже находится в коллективном режиме.
{
shrd_lck sl2 {shared_mut};
cout << "shared lock twice.n";
print_exclusive();
}
8. После выхода из самой поздней области видимости деструктор объекта sl2 типа shared_lock освобождает свою общую блокировку мьютекса. Функция print_exclusive снова даст сбой, поскольку мьютекс все еще находится в коллективном режиме блокировки.
cout << "shared lock once again.n";
print_exclusive();
}
cout << "lock is free.n";
9. После выхода из второй области видимости все объекты типа shared_lock подвергнутся уничтожению и мьютекс снова будет находиться в разблокированном состоянии. Теперь наконец можно заблокировать мьютекс в эксклюзивном режиме. Сделаем это путем вызовов exclusive_throw и print_exclusive. Помните, что мы генерируем исключение в вызове exclusive_throw. Но поскольку unique_lock — это объект RAII, который помогает защититься от исключений, мьютекс снова будет разблокирован независимо от того, что вернет вызов exclusive_throw. Таким образом, функция print_exclusive не будет ошибочно блокировать все еще заблокированный мьютекс:
try {
exclusive_throw();
} catch (int e) {
cout << "Got exception " << e << 'n';
}
print_exclusive();
}
10. Компиляция и запуск программы дадут следующий результат. Первые две строки показывают наличие двух экземпляров общей блокировки. Затем функция print_exclusive дает сбой при попытке заблокировать мьютекс в эксклюзивном режиме. После того как мы покинем внутреннюю область видимости и разблокируем вторую общую блокировку, функция print_exclusive все еще будет давать сбой. После выхода из второй области видимости, что наконец снова освободит мьютекс, функции exclusive_throw и print_exclusive смогут заблокировать мьютекс:
$ ./shared_lock
shared lock once.
shared lock twice.
Unable to lock exclusively.
shared lock once again.
Unable to lock