Эффективный и современный С++. 42 рекомендации по использованию С++11 и С++14 - [110]

Шрифт
Интервал

• Неявный вызов>join. В этом случае деструктор >std::thread будет ожидать завершения соответствующего асинхронного потока. Звучит разумно, но может привести к аномалиям производительности, которые будет трудно отследить. Например, было бы нелогичным, чтобы функция >doWork ожидала применения фильтра ко всем значениям, если вызов >conditionsAreSatisfied() уже вернул значение >false.

• Неявный вызов>detach. В этом случае деструктор >std::thread разрывает связь между объектом >std::thread и его потоком, отключая последний от объекта. Поток продолжает выполняться. Это звучит не менее разумно, чем применение >join, но проблемы отладки, к которым это может привести, делают этот вариант еще худшим. Например, в функции >doWork локальная переменная >goodVals захватывается лямбда-выражением по ссылке. Она также изменяется в лямбда-выражении (с помощью вызова >push_back). Предположим теперь, что во время асинхронного выполнения лямбда-выражения вызов >conditionsAreSatisfied() вернул >false. В таком случае функция >doWork должна завершиться, а ее локальные переменные (включая >goodVals) должны быть уничтожены. Ее кадр стека удаляется, и выполнение потока продолжается с точки вызова >doWork.

Инструкции после этой точки могут в некоторой иной точке выполнять вызовы других функций, и как минимум одна из них может занять место в стеке, ранее занятое кадром стека >doWork. Назовем эту функцию >f. Во время работы >f лямбда-выражение из >doWork продолжает асинхронно выполняться и может вызвать >push_back для памяти в стеке, которая ранее использовалась для локальной переменной >goodVals, а теперь принадлежит кадру стека >f. Такой вызов может изменить память, использовавшуюся для >goodVals, а это означает, что с точки зрения >f содержимое памяти в ее кадре стека может внезапно измениться! Только представьте себе, что вам придется отлаживать такое.

Комитет по стандартизации решил, что последствия уничтожения подключаемого потока достаточно неприятны, чтобы полностью их запретить (указав, что уничтожение подключаемого потока приводит к завершению программы).

Это решение возлагает на вас ответственность за то, что если вы используете объект >std::thread, то должны сделать его неподключаемым на всех путях из области видимости, в которой он определен. Но охват всех путей может быть весьма сложным. Он включает как нормальный выход из области видимости, так и такие выходы, как с помощью >return, >continue, >break, >goto или исключения. Этих путей может быть великое множество.

Всякий раз, когда надо выполнить некоторое действие на всех путях, ведущих из блока, естественным подходом является размещение этого действия в деструкторе локального объекта. Такие объекты известны как объекты RAII, а соответствующие классы — классы RAII. (RAII означает “Resource Acquisition Is Initialization”, “захват ресурса есть инициализация”, хотя на самом деле речь идет о методе деструкции, а не инициализации.) RAII-классы — распространенное явление в стандартной библиотеке. Примерами являются контейнеры STL (деструктор каждого контейнера уничтожает его содержимое и освобождает память), стандартные интеллектуальные указатели (в разделах 4.1-4.3 поясняется, что деструктор >std::unique_ptr вызывает удалитель для объекта, на который указывает, а деструкторы >std::shared_ptr и >std::weak_ptr уменьшают счетчики ссылок), объекты >std::fstream (их деструкторы закрывают соответствующие файлы) и многое другое. Тем не менее стандартного RAII-класса для объектов >std::thread нет, вероятно, потому что Комитет по стандартизации, отвергнув и >join, и >detach как варианты по умолчанию, просто не знал, что же должен делать такой класс.

К счастью, такой класс несложно написать самостоятельно. Например, приведенный далее класс позволяет вызывающей функции указать, должна ли быть вызвана функция-член >join или >detach при уничтожении объекта >ThreadRAII (объект RAII для >std::thread):

>class ThreadRAII {

>public:

> enum class DtorAction { join, detach };   // См. enum class

>                                           // в разделе 3.4

> ThreadRAII(std::thread&& t, DtorAction а) // Деструктор для

> : action(a), t(std::move(t)) {}           // t выполняет а

> ~ThreadRAII() {                           // См. ниже о

>  if (t.joinable()) {                      // проверке на

>   if (action == DtorAction::join) {       // подключаемость

>    t.join();

>   } else {

>    t.detach();

>   }

>  }

> }

> std::thread& get() { return t; }          // См. ниже

>private:

> DtorAction action;

> std::thread t;

>};

Я надеюсь, что этот код самодостаточен и не требует особых пояснений, тем не менее может быть полезно остановиться на следующих моментах.

• Конструктор принимает только rvalue >std::thread, поскольку мы хотим перемещать передаваемый объект >std::thread в объект ThreadRAII. (Вспомните, что объекты >std::thread не копируются.)

• Порядок параметров выбран интуитивно понятным для программиста (сначала указывается поток, а затем — способ его деструкции), но список инициализации членов соответствует порядку объявлений членов-данных. Этот порядок размещает объект >std::thread последним. В этом классе порядок не имеет значения, но в общем случае возможна ситуация, когда инициализация одного члена-данных зависит от другого, а поскольку объекты


Еще от автора Скотт Мейерс
Эффективное использование STL

В этой книге известный автор Скотт Мейерс раскрывает секреты настоящих мастеров, позволяющие добиться максимальной эффективности при работе с библиотекой STL.Во многих книгах описываются возможности STL, но только в этой рассказано о том, как работать с этой библиотекой. Каждый из 50 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


Как функции, не являющиеся методами, улучшают инкапсуляцию

Когда приходится инкапсулировать, то иногда лучше меньше, чем большеЯ начну со следующего утверждения: Если вы пишете функцию, которая может быть выполнена или как метод класса, или быть внешней по отношению к классу, Вы должны предпочесть ее реализацию без использования метода. Такое решение увеличивает инкапсуляцию класса. Когда Вы думаете об использовании инкапсуляции, Вы должны думать том, чтобы не использовать методы.Удивлены? Читайте дальше.


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Симуляция частичной специализации

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Обработка событий в С++

В книге рассказывается история главного героя, который сталкивается с различными проблемами и препятствиями на протяжении всего своего путешествия. По пути он встречает множество второстепенных персонажей, которые играют важные роли в истории. Благодаря опыту главного героя книга исследует такие темы, как любовь, потеря, надежда и стойкость. По мере того, как главный герой преодолевает свои трудности, он усваивает ценные уроки жизни и растет как личность.


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.