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

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

(spurious wakeup). Корректный код обрабатывает такую ситуацию, проверяя, что ожидаемое условие в действительности выполнено, и это делается первым, немедленно после пробуждения. API переменных условия С++ делает это исключительно простым, поскольку допускает применение лямбда-выражений (или иных функциональных объектов), которые проверяют условие, переданное в >wait. Таким образом, вызов >wait в задаче реакции может быть записан следующим образом:

>cv.wait(lk,

>        []{ return Произошло ли событие; });

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

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

Многие разработчики используют совместно используемый булев флаг. Изначально этот флаг имеет значение >false. Когда поток обнаружения распознает ожидаемое событие, он устанавливает этот флаг:

>std::atomic flag(false); // Совместно используем флаг;

>                               // std::atomic см. в разделе 7.6

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

>flag = true;                   // Сообщение задаче обнаружения

Со своей стороны поток реакции просто опрашивает флаг. Когда он видит, что флаг установлен, он знает, что ожидаемое событие произошло:

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

>while (!flag); // Ожидание события

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

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

Куда менее замечательно выглядит стоимость опроса в задаче реакции. Во время ожидания флага задача, по сути, заблокирована, но продолжает выполняться. А раз так, она занимает аппаратный поток, который мог бы использоваться другой задачей, требует стоимости переключения контекста при каждом начале и завершении выделенных потоку временных промежутков и заставляет работать ядро, которое в противном случае могло бы быть отключено для экономии энергии. При настоящей блокировке задача не делает ничего из перечисленного. Это является преимуществом подхода на основе переменных условия, поскольку блокировка задачи при вызове >wait является истинной.

Распространено сочетание подходов на основе переменных условия и флагов. Флаг указывает, произошло ли интересующее нас событие, но доступ к флагу синхронизирован мьютексом. Поскольку мьютекс предотвращает параллельный доступ к флагу, не требуется, как поясняется в разделе 7.6, чтобы флаг был объявлен как >std::atomic; вполне достаточно простого >bool. Задача обнаружения в этом случае может имеет следующий вид:

>std::condition_variable cv;        // Как и ранее

>std::mutex m;

>bool flag(false);                  // Не std::atomic


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

>{

> std::lock_guard g(m); // Блокировка m

>                                   // конструктором g


> flag = true;                      // Сообщаем задаче реакции

>                                   // (часть 1)


>}                                  // Снятие блокировки m

>                                   // деструктором g


> cv.notify_one();                  // Сообщаем задаче реакции

>                                   // (часть 2)

А вот задача реакции:

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


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

> std: :unique_lock lk(m); // Как и ранее


> cv.wait(lk, []{ return flag; });     // Применение лямбда-

>                                      // выражения во избежание

>                                      // ложных пробуждений


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

>                                      // (m заблокирован)

>}

>…                                     // Продолжение реакции

>                                      // (m разблокирован)

Этот подход позволяет избежать проблем, которые мы обсуждали. Он работает независимо от того, вызывает ли задача реакции >wait до уведомления задачей обнаружения, он работает при наличии ложных пробуждений и не требует опроса флага. Тем не менее душок остается, потому что задача обнаружения взаимодействует с задачей реакции очень любопытным способом. Уведомляемая переменная условия говорит задаче реакции о том, что, вероятно, произошло ожидаемое событие, но задаче реакции необходимо проверить флаг, чтобы быть в этом уверенной. Установка флага говорит задаче реакции, что событие, определенно, произошло, но задача обнаружения по-прежнему обязана уведомить переменную условия о том, чтобы задача реакции активизировалась и проверила флаг. Этот подход работает, но не кажется очень чистым.


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