Эффективный и современный С++. 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 советов книги подкреплен анализом и убедительными примерами, поэтому читатель не только узнает, как решать ту или иную задачу, но и когда следует выбирать то или иное решение — и почему именно такое.


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

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


Рекомендуем почитать
Изучаем Java EE 7

Java Enterprise Edition (Java EE) остается одной из ведущих технологий и платформ на основе Java. Данная книга представляет собой логичное пошаговое руководство, в котором подробно описаны многие спецификации и эталонные реализации Java EE 7. Работа с ними продемонстрирована на практических примерах. В этом фундаментальном издании также используется новейшая версия инструмента GlassFish, предназначенного для развертывания и администрирования примеров кода. Книга написана ведущим специалистом по обработке запросов на спецификацию Java EE, членом наблюдательного совета организации Java Community Process (JCP)


Платформа J2Me

Эта книга научит вас, как разрабатывать программное обеспечение для платформы J2ME компании «Sun Microsystems». Эта книга придерживается стиля учебного пособия, это не справочное руководство.Цель — дать вам твердую основу в понятиях и техниках, которая даст вам возможность решиться на самостоятельную разработку качественных приложений.


Виртуальная библиотека Delphi

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


Обработка баз данных на Visual Basic.NET

Это практическое руководство разработчика программного обеспечения на Visual Basic .NET и ADO.NET, предназначенное для создания приложений баз данных на основе WinForms, Web-форм и Web-служб. В книге описываются практические способы решения задач доступа к данным, с которыми сталкиваются разработчики на Visual Basic .NET в своей повседневной деятельности. Книга начинается с основных сведений о создании баз данных, использовании языка структурированных запросов SQL и системы управления базами данных Microsoft SQL Server 2000.


Исчерпывающее руководство по написанию всплывающих подсказок

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


Программное обеспечение встроенных систем. Общие требования к разработке и документированию

Embedded system software. General requirements for development and documentationСтандарт подготовлен в развитие ГОСТ Р ИСО/МЭК 12207-99 «Информационная технология. Процессы жизненного цикла программных средств» с целью учета специфики разработки и документирования программного обеспечения встроенных систем реального времени.