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

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

>std::thread t(doAsyncWork);

Вы также можете передать >doAsyncWork в >std::async, воспользовавшись стратегией, известной как подход на основе задач:

>auto fut = std::async(doAsyncWork); // "fut" от "future"

В таких вызовах функциональный объект, переданный в >std::async (например, >doAsyncWork), рассматривается как задача (task).

Подход на основе задач обычно превосходит свой аналог на основе потоков, и небольшие фрагменты кода, с которыми вы встретились выше, показывают некоторые причины этого. Здесь >doAsyncWork дает возвращаемое значение, в котором, как мы можем разумно предположить, заинтересован вызывающий >doAsyncWork код. В случае вызова на основе потоков нет простого способа к нему обратиться. При подходе на основе потоков нет простого способа получить доступ к вызову. В случае же подхода на основе задач это можно легко сделать, поскольку фьючерс, возвращаемый >std::async, предлагает функцию >get. Эта функция >get еще более важна, если >doAsyncWork генерирует исключение, поскольку >get обеспечивает доступ и к нему. При подходе на основе потоков в случае генерации функцией >doAsyncWork исключения программа аварийно завершается (с помощью вызова >std::terminate).

Более фундаментальным различием между подходами на основе потоков и на основе задач является воплощение более высокого уровня абстракции в последнем. Он освобождает вас от деталей управления потоками, что, кстати, напомнило мне о необходимости рассказать о трех значениях слова поток (thread) в параллельном программировании на С++.

• Аппаратные потоки являются потоками, которые выполняют фактические вычисления. Современные машинные архитектуры предлагают по одному или по нескольку аппаратных потоков для каждого ядра процессора.

• Программные потоки (известные также как потоки ОС или системные потоки) являются потоками, управляемыми операционной системой[21] во всех процессах и планируемыми для выполнения аппаратными потоками. Обычно можно создать программных потоков больше, чем аппаратных, поскольку, когда программный поток заблокирован (например, при вводе-выводе или ожидании мьютекса или переменной условия), пропускная способность может быть повышена путем выполнения других, незаблокированных потоков.

• >std::thread представляют собой объекты в процессе С++, которые действуют как дескрипторы для лежащих в их основе программных потоков. Некоторые объекты >std::thread представляют “нулевые” дескрипторы, т.e. не соответствуют программным потокам, поскольку находятся в состоянии, сконструированном по умолчанию (следовательно, без выполняемой функции); потоки, из которых выполнено перемещение (после перемещения объект >std::thread, в который оно произошло, действует как дескриптор для соответствующего программного потока); потоки, у которых выполняемая ими функция завершена; а также потоки, у которых разорвана связь между ними и обслуживающими их программными потоками.

Программные потоки являются ограниченным ресурсом. Если вы попытаетесь создать их больше, чем может предоставить система, будет сгенерировано исключение >std::system_error. Это так, даже если функция, которую вы хотите запустить, не генерирует исключений. Например, даже если >doAsyncWork объявлена как >noexcept,

>int doAsyncWork() noexcept; // См. noexcept в разделе 3.8

следующая инструкция может сгенерировать исключение:

>std::thread t(doAsyncWork); // Генерация исключения, если

>                            // больше нет доступных потоков

Хорошо написанное программное обеспечение должно каким-то образом обрабатывать такую возможность, но как? Один вариант — запустить >doAsyncWork в текущем потоке, но это может привести к несбалансированной нагрузке и, если текущий поток является потоком GUI, к повышенному времени реакции системы на действия оператора. Другой вариант — ожидание завершения некоторых существующих программных потоков с последующей попыткой создания нового объекта >std::thread, но может быть и так, что существующие потоки ожидают действий, которые должна выполнить функция >doAsyncWork (например, ее результата или уведомления переменной условия).

Даже если вы не исчерпали потоки, у вас могут быть проблемы с превышением подписки (oversubscription). Это происходит, когда имеется больше готовых к запуску (т.e. незаблокированных) программных потоков, чем аппаратных. Когда это случается, планировщик потоков (который обычно представляет собой часть операционной системы) выполняет разделение времени для выполнения программных потоков аппаратными. Когда время работы одного потока завершается и начинается время работы второго, выполняется переключение контекстов. Такие переключения контекстов увеличивают накладные расходы по управлению потоками и могут оказаться в особенности дорогостоящими, когда аппаратный поток, назначаемый программному, оказывается выполняемым другим ядром, не тем, что ранее. В этом случае (1) кеши процессора обычно оказываются с данными, не имеющими отношения к данному программному потоку, и (2) запуск “нового” программного потока на этом ядре “загрязняет” кеши процессора, заполняя их данными, не имеющими отношения к “старым” потокам, которые выполнялись этим ядром и, вероятно, будут выполняться им снова.


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