chitay-knigi.com » Разная литература » C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 97 98 99 100 101 102 103 104 105 ... 121
Перейти на страницу:
то можно вызвать функцию x.detach(). После этого у нас не будет возможности управлять потоком. Независимо от принятого решения, мы должны всегда объединять или откреплять потоки. Если мы не сделаем этого, то деструктор объекта thread вызовет функцию std::terminate(), что приведет к внезапному завершению работы приложения.

В момент, когда функция 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

1 ... 97 98 99 100 101 102 103 104 105 ... 121
Перейти на страницу:

Комментарии
Минимальная длина комментария - 25 символов.
Комментариев еще нет. Будьте первым.