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

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

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

• ThreadRAII предоставляет функцию >get, обеспечивающую доступ к соответствующему объекту >std::thread. Это аналог функций >get, предоставляемых стандартными интеллектуальными указателями и обеспечивающих доступ к их базовым обычным указателям. Предоставление >get позволяет избежать необходимости дублировать в классе ThreadRAII весь интерфейс >std::thread, а также означает, что объекты ThreadRAII могут использоваться в контекстах, где требуются объекты >std::thread.

• Перед тем как деструктор ThreadRAII вызовет функцию-член объекта t типа >std::thread, он проверяет, является ли этот объект подключаемым. Это необходимо, поскольку применение >join или >detach к неподключаемому объекту приводит к неопределенному поведению. Может быть так, что клиент создает >std:: thread, затем создает из него объект ThreadRAII, использует функцию-член >get для получения доступа к >t, а затем выполняет перемещение из >t или вызывает для него >join или >detach. Каждое из этих действий делает >t неподключаемым.

>if (t.joinable()) {

> if (action == DtorAction::join) {

>  t.join();

> } else {

>  t.detach();

> }

>}

Если в приведенном фрагменте вас беспокоит возможность условия гонки из-за того, что между вызовами >t.joinable() и >join или >detach другой поток может сделать >t неподключаемым, то ваша интуиция заслуживает похвалы, но ваши опасения в данном случае беспочвенны. Объект >std::thread может изменить состояние с подключаемого на неподключаемое только путем вызова функции-члена, например >join, >detach или операции перемещения. В момент вызова деструктора ThreadRAII никакие другие потоки не должны вызывать функцию-член для этого объекта. При наличии одновременных вызовов, определенно, имеется условие гонки, но не внутри деструктора, а в клиентском коде, который пытается вызвать одновременно две функции-члена объекта (деструктор и что-то еще). В общем случае одновременные вызовы функций-членов для одного объекта безопасны, только если все они являются константными функциями-членами (см. раздел 3.10).

Использование >ThreadRAII в нашей функции >doWork может выглядеть следующим образом:

>bool doWork(std::function filter, // Как и ранее

>            int maxVal = tenMillion)

>{

> std::vector goodVals;                  // Как и ранее

> ThreadRAII t( // use RAII object

>  std::thread([&filter, maxVal, &goodVals] {

>   for (auto i = 0; i <= maxVal; ++i)

>   { if (filter(i)) goodVals.push_back(i); }

>  }),

>  ThreaRAII::DtorAction::join                // Действие RAII

> );


> auto nh = t.get().native_handle();

> …

> if (conditionsAreSatisfied()) {

>  t.get().join();

performComputation(goodVals);

>  return true;

> }

> return false;

>}

В этом случае мы выбрали использование >join для асинхронно выполняющегося потока в деструкторе >ThreadRAII, поскольку, как мы видели ранее, применение >detach может привести к настоящим кошмарам при отладке. Мы также видели ранее, что применение >join может вести к аномалиям производительности (что, откровенно говоря, также может быть неприятно при отладке), но выбор между неопределенным поведением (к которому ведет >detach), завершением программы (при использовании обычного >std::thread) и аномалиями производительности предопределен — мы выбираем меньшее из зол.

Увы, раздел 7.5 демонстрирует, что применение >ThreadRAII для выполнения >join при уничтожении >std::thread иногда может привести не к аномалии производительности, а к полному “зависанию” программы. “Правильным” решением этого типа проблем было бы сообщить асинхронно выполняющемуся лямбда-выражению, что в его услугах мы больше не нуждаемся и оно должно поскорее завершиться. Увы, С++11 не поддерживает прерываемые потоки. Их можно реализовать вручную, но данный вопрос выходит за рамки нашей книги[24].

В разделе 3.11 поясняется, что, поскольку >ThreadRAII объявляет деструктор, в нем нет генерируемых компилятором перемещающих операций, но причин, по которым >ThreadRAII не должен быть перемещаемым, тоже нет. Если бы компилятор генерировал такие функции, то они демонстрировали бы верное поведение, так что просто попросим компилятор их все же сгенерировать:

>class ThreadRAII {

>public:

> enum class DtorAction { join, detach };        // Как и ранее


> ThreadRAII(std::thread&& t, DtorAction a)      // Как и ранее

> : action(а), t(std::move(t)) {}

> ~ThreadRAII() {

>  …                                             // Как и ранее

> }


> ThreadRAII(ThreadRAII&&) = default;            // Поддержка

> ThreadRAII& operator=(ThreadRAII&&) = default; // перемещения


> std::threads get() { return t; }               // Как и ранее


>private:                                        // Как и ранее

> DtorAction action;

> std::thread t;

>};

Следует запомнить

• Делайте >std::thread неподключаемыми на всех путях выполнения.

• Применение >join при уничтожении объекта может привести к трудно отлаживаемым аномалиям производительности.


Еще от автора Скотт Мейерс
Эффективное использование 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 так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.