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

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

Альтернативный вариант заключается в том, чтобы избежать переменных условия, мьютексов и флагов с помощью вызова >wait задачей реакции для фьючерса, установленного задачей обнаружения. Это может показаться странной идеей. В конце концов, в разделе 7.4 поясняется, что фьючерс представляет принимающий конец канала связи от вызываемой функции к (обычно асинхронной) вызывающей функции, а между задачами обнаружения и реакции нет отношений “вызываемая-вызывающая”. Однако в разделе 7.4 также отмечается, что канал связи, передающий конец которого представляет собой >std::promise, а принимающий — фьючерс, может использоваться для большего, чем простой обмен информацией между вызываемой и вызывающей функциями. Такой канал связи может быть использован в любой ситуации, в которой необходима передача информации из одного места вашей программы в другое. В нашем случае мы воспользуемся им для передачи информации от задачи обнаружения задаче реакции, и информация, которую мы будем передавать, — о том, что произошло интересующее нас событие.

Проект прост. Задача обнаружения имеет объект >std::promise (т.e. передающий конец канала связи), а задача реакции имеет соответствующий фьючерс. Когда задача обнаружения видит, что произошло ожидаемое событие, она устанавливает объект >std::promise (т.e. выполняет запись в канал связи). Тем временем задача реакции выполняет вызов >wait своего фьючерса. Этот вызов >wait блокирует задачу реакции до тех пор, пока не будет установлен объект >std::promise.

И >std::promise, и фьючерсы (т.e. >std::future и >std::shared_ future) являются шаблонами, требующими параметр типа. Этот параметр указывает тип данных, передаваемый по каналу связи. Однако в нашем случае никакие данные не передаются. Единственное, что представляет интерес для задачи реакции, — что ее фьючерс установлен. Нам нужно указать для шаблонов >std::promise и фьючерса тип, показывающий, что по каналу связи не будут передаваться никакие данные. Таким типом является >void. Задача обнаружения, таким образом, будет использовать >std::promise, а задача реакции — >std::future или >std::shared_future. Задача обнаружения устанавливает свой объект >std::promise, когда происходит интересующее нас событие, а задача реакции ожидает с помощью вызова >wait своего фьючерса. Даже несмотря на то, что задача реакции не получает никаких данных от задачи обнаружения, канал связи позволит задаче реакции узнать, что задача обнаружения “записала” >void-данные с помощью вызова >set_value своего объекта >std::promise. Так что для данного

>std::promise p;  // Коммуникационный канал

код задачи обнаружения тривиален:

>…                      // Обнаружение события

>p.set_value();         // Сообщение задаче реакции

Код задачи реакции не менее прост:

>…                      // Подготовка к реакции

>p.get_future().wait(); // Ожидание фьючерса,

>                       // соответствующего p

>…                      // Реакция на событие

Этот подход, как и использование флага, не требует мьютексов, работает независимо от того, устанавливает ли задача обнаружения свой объект >std::promise до того, как задача реакции вызывает >wait, и невосприимчива к ложным пробуждениям. (Этой проблеме подвержены только переменные условия.) Подобно подходу на основе переменных условия задача реакции оказывается истинно заблокированной после вызова >wait, так что во время ожидания не потребляет системные ресурсы. Идеально, нет?

Не совсем. Конечно, подход на основе фьючерсов обходит описанные неприятности, но ведь есть и другие. Например, в разделе 7.4 поясняется, что между >std::promise и фьючерсом находится общее состояние, а общие состояния обычно выделяются динамически. Поэтому следует предполагать, что данное решение приводит к расходам на динамическое выделение и освобождение памяти.

Вероятно, еще более важно то, что >std::promise может быть установлен только один раз. Канал связи между >std::promise и фьючерсом является одноразовым механизмом: он не может быть использован многократно. Это существенное отличие от применения переменных условия и флагов, которые могут использоваться для связи много раз. (Переменная условия может быть уведомлена неоднократно, а флаг может сбрасываться и устанавливаться вновь.)

Ограничение однократности не столь тяжкое, как можно подумать. Предположим, что вы хотите создать системный поток в приостановленном состоянии. То есть вы хотели бы заплатить все накладные расходы, связанные с созданием потока, заранее, с тем, чтобы как только вы будете готовы выполнить что-то в этом потоке, вы сможете делать это сразу, без задержки. Или, может быть, вы захотите создать поток в приостановленном состоянии с тем, чтобы можно было настроить его перед выполнением. Такая настройка может включать, например, установку приоритета. API параллельных вычислений С++ не предоставляет способ выполнить такие действия, но объекты >std::thread предлагают функцию-член >native_handle, результат которой призван предоставить доступ к API многопоточности используемой платформы (обычно потокам POSIX или Windows). Низкоуровневые API часто позволяют настраивать такие характеристики потоков, как приоритет или сродство.


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