Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - [39]

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

>  std::unique_lock lk(mut);

>  data_cond.wait(lk, [this]{return !data_queue.empty();});

>  value = data_queue.front();

>  data_queue.pop();

> }

>};


>threadsafe_queue data_queue; ←(1)


>void data_preparation_thread() {

> while (more_data_to_prepare()) {

>  data_chunk const data = prepare_data();

>  data_queue.push(data); ←(2)

> }

>}


>void data_processing_thread() {

> while (true) {

>  data_chunk data;

>  data_queue.wait_and_pop(data); ←(3)

>  process(data);

>  if (is_last_chunk(data))

>   break;

> }

>}

Теперь мьютекс и условная переменная находятся в экземпляре >threadsafe_queue, поэтому не нужно ни отдельных переменных (1), ни внешней синхронизации при обращении к функции >push()(2). Кроме того, >wait_and_pop() берет на себя заботу об ожидании условной переменной (3).

Второй перегруженный вариант >wait_and_pop() тривиален, а остальные функции можно почти без изменений скопировать из кода стека в листинге 3.5. Ниже приведена окончательная реализация.


Листинг 4.5. Полное определение класса потокобезопасной очереди на базе условных переменных

>#include

>#include

>#include

>#include


>template

>class threadsafe_queue {

>private:

> mutable std::mutex mut;←(1) Мьютекс должен быть изменяемым

> std::queue data_queue;

> std::condition_variable data_cond;


>public:

> threadsafe_queue() {}

> threadsafe_queue(threadsafe_queue const& other) {

>  std::lock_guard lk(other.mut);

>  data_queue = other.data_queue;

> }


> void push(T new_value) {

>  std::lock_guard lk(mut);

>  data_queue.push(new_value);

>  data_cond.notify_one();

> }


> void wait_and_pop(T& value) {

>  std::unique_lock lk(mut);

>  data_cond.wait(lk, [this]{ return !data_queue.empty(); });

>  value = data_queue.front();

>  data_queue.pop();

> }


> std::shared_ptr wait_and_pop() {

>  std::unique_lock lk(mut);

>  data_cond.wait(lk, [this]{ return !data_queue.empty(); });

>  std::shared_ptr

>   res(std::make_shared(data_queue.front()));

>  data_queue.pop();

>  return res;

> }


> bool try_pop(T& value) {

>  std::lock_guard lk(mut);

>  if (data_queue.empty())

>   return false;

>  value = data_queue.front();

>  data_queue.pop();

>  return true;

> }


> std::shared_ptr try_pop() {

>  std::lock_guard lk(mut);

>  if (data_queue.empty())

>   return std::shared_ptr();

>  std::shared_ptr

>   res(std::make_shared(data_queue.front()));

>  data_queue.pop();

>  return res;

> }


> bool empty() const {

>  std::lock_guard lk(mut);

>  return data_queue.empty();

> }

>};

Хотя >empty() — константная функция-член, а параметр копирующего конструктора — >const-ссылка, другие потоки могут хранить неконстантные ссылки на объект и вызывать изменяющие функции-члены, которые захватывают мьютекс. Поэтому захват мьютекса — это изменяющая операция, следовательно, член >mut необходимо пометить как >mutable(1), чтобы его можно было захватить в функции >empty() и в копирующем конструкторе.

Условные переменные полезны и тогда, когда есть несколько потоков, ожидающих одного события. Если потоки используются для разделения работы и, следовательно, на извещение должен реагировать только один поток, то применима точно такая же структура программы, как в листинге 4.1; нужно только запустить несколько потоков обработки данных. При поступлении новых данных функция >notify_one() разбудит только один поток, который проверяет условие внутри >wait(), и этот единственный поток вернет управление из >wait() (в ответ на помещение нового элемента в очередь >data_queue). Заранее нельзя сказать, какой поток получит извещение и есть ли вообще ожидающие потоки (не исключено, что все они заняты обработкой ранее поступивших данных).

Альтернативный сценарий — когда несколько потоков ожидают одного события, и отреагировать должны все. Так бывает, например, когда инициализируются разделяемые данные, и все работающие с ними потоки должны ждать, пока инициализация завершится (хотя для этого случая существуют более подходящие механизмы, см. раздел 3.3.1 главы 3), или когда потоки должны ждать обновления разделяемых данных, например, в случае периодической повторной инициализации. В таких ситуациях поток, отвечающий за подготовку данных, может вызвать функцию-член >notify_all() условной переменной вместо >notify_one(). Эта функция извещает все потоки, ожидающие внутри функции >wait(), о том, что они должны проверить ожидаемое условие.

Если ожидающий поток собирается ждать условия только один раз, то есть после того как оно станет истинным, он не вернется к ожиданию той же условной переменной, то лучше применить другой механизм синхронизации. В особенности это относится к случаю, когда ожидаемое условие — доступность каких-то данных. Для такого сценария больше подходят так называемые будущие результаты (future).

4.2. Ожидание одноразовых событий с помощью механизма будущих результатов

Предположим, вы летите самолетом в отпуск за границу. Вы приехали в аэропорт, прошли регистрацию и прочие процедуры, но должны ждать объявления о посадке — быть может, несколько часов. Можно, конечно, найти себе занятие — например, почитать книжку, побродить в Интернете, поесть в кафе за бешеные деньги, но суть от этого не меняется: вы ждете сигнала о том, что началась посадка в самолет. И есть еще одна особенность — данный рейс вылетает всего один раз; в следующий отпуск вы будете ждать посадки на другой рейс.


Еще от автора Энтони Д Уильямс
Викиномика. Как массовое сотрудничество изменяет всё

Это знаменитый бестселлер, который научит вас использовать власть массового сотрудничества и покажет, как применять викиномику в вашем бизнесе. Переведенная более чем на двадцать языков и неоднократно номинированная на звание лучшей бизнес-книги, "Викиномика" стала обязательным чтением для деловых людей во всем мире. Она разъясняет, как массовое сотрудничество происходит не только на сайтах Wikipedia и YouTube, но и в традиционных компаниях, использующих технологии для того, чтобы вдохнуть новую жизнь в свои предприятия.Дон Тапскотт и Энтони Уильямс раскрывают принципы викиномики и рассказывают потрясающие истории о том, как массы людей (как за деньги, так и добровольно) создают новости, изучают геном человека, создают ремиксы любимой музыки, находят лекарства от болезней, редактируют школьные учебники, изобретают новую косметику, пишут программное обеспечение и даже строят мотоциклы.Знания, ресурсы и вычислительные способности миллиардов людей самоорганизуются и превращаются в новую значительную коллективную силу, действующую согласованно и управляемую с помощью блогов, вики, чатов, сетей равноправных партнеров и личные трансляции.


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

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


Геймдизайн. Рецепты успеха лучших компьютерных игр от Super Mario и Doom до Assassin’s Creed и дальше

Что такое ГЕЙМДИЗАЙН? Это не код, графика или звук. Это не создание персонажей или раскрашивание игрового поля. Геймдизайн – это симулятор мечты, набор правил, благодаря которым игра оживает. Как создать игру, которую полюбят, от которой не смогут оторваться? Знаменитый геймдизайнер Тайнан Сильвестр на примере кейсов из самых популярных игр рассказывает как объединить эмоции и впечатления, игровую механику и мотивацию игроков. Познакомитесь с принципами дизайна, которыми пользуются ведущие студии мира! Создайте игровую механику, вызывающую эмоции и обеспечивающую разнообразие.


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

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


MFC и OpenGL

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


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

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


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

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