Параллельное программирование на С++ в действии. Практика разработки многопоточных программ - [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, но и в традиционных компаниях, использующих технологии для того, чтобы вдохнуть новую жизнь в свои предприятия.Дон Тапскотт и Энтони Уильямс раскрывают принципы викиномики и рассказывают потрясающие истории о том, как массы людей (как за деньги, так и добровольно) создают новости, изучают геном человека, создают ремиксы любимой музыки, находят лекарства от болезней, редактируют школьные учебники, изобретают новую косметику, пишут программное обеспечение и даже строят мотоциклы.Знания, ресурсы и вычислительные способности миллиардов людей самоорганизуются и превращаются в новую значительную коллективную силу, действующую согласованно и управляемую с помощью блогов, вики, чатов, сетей равноправных партнеров и личные трансляции.


Рекомендуем почитать
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 так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.