В этом примере мы использовали класс std::packaged_task
для задач, обертывающих функцию или иной допускающий вызов объект, который не принимает параметров и возвращает void
(если он вернет что-то другое, то возвращенное значение будет отброшено). Это простейшая из всех возможных задач, но, как мы видели ранее, шаблон std::packaged_task
применим и в более сложных ситуациях — задав другую сигнатуру функции в качестве параметра шаблона, вы сможете изменить тип возвращаемого значения (и, стало быть, тип данных, которые хранятся в состоянии, ассоциированном с будущим объектом), а также типы аргументов оператора вызова. Наш пример легко обобщается на задачи, которые должны выполняться в потоке GUI и при этом принимают аргументы и возвращают в std::future
какие-то данные, а не только индикатор успешности завершения.
А как быть с задачами, которые нельзя выразить в виде простого вызова функции, или такими, где результат может поступать из нескольких мест? Эти проблемы решаются с помощью еще одного способа создания будущего результата: явного задания значения с помощью шаблона класса std::promise
[8] promise — обещание. Прим. перев.
.
4.2.3. Использование std::promise
При написании сетевых серверных программ часто возникает искушение обрабатывать каждый запрос на соединение в отдельном потоке, поскольку при такой структуре порядок коммуникации становится нагляднее и проще для программирования. Этот подход срабатывает, пока количество соединений (и, следовательно, потоков) не слишком велико. Но с ростом числа потоков увеличивается и объем потребляемых ресурсов операционной системы, а равно частота контекстных переключений (если число потоков превышает уровень аппаратного параллелизма), что негативно сказывается на производительности. В предельном случае у операционной системы могут закончиться ресурсы для запуска новых потоков, хотя пропускная способность сети еще не исчерпана. Поэтому в приложениях, обслуживающих очень большое число соединений, обычно создают совсем немного потоков (быть может, всего один), каждый из которых одновременно обрабатывает несколько запросов.
Рассмотрим один из таких потоков. Пакеты данных приходят по разным соединениям в случайном порядке, а потому и порядок помещения исходящих пакетов в очередь отправки тоже непредсказуем. Часто будет складываться ситуация, когда другие части приложения ждут либо успешной отправки данных, либо поступления нового пакета по конкретному сетевому соединению.
Шаблон std::promise
дает возможность задать значение (типа T
), которое впоследствии можно будет прочитать с помощью ассоциированного объекта std::future
. Пара std::promise
/ std::future
реализует один из возможных механизмов такого рода; ожидающий поток приостанавливается в ожидании будущего результата, тогда как поток, поставляющий данные, может с помощью promise
установить ассоциированное значение и сделать будущий результат готовым .
Чтобы получить объект std::future
, ассоциированный с данным обещанием std::promise
, мы должны вызвать функцию-член get_future()
— так же, как в случае std::packaged_task
. После установки значения обещания (с помощью функции-члена set_value()
) будущий результат становится готовым , и его можно использовать для получения установленного значения. Если уничтожить объект std::promise
, не установив значение, то в будущем результате будет сохранено исключение. О передаче исключений между потоками см. раздел 4.2.4.
В листинге 4.10 приведен код потока обработки соединений, написанный по только что изложенной схеме. В данном случае для уведомления об успешной передаче блока исходящих данных применяется пара std::promise
/ std::future
; ассоциированное с будущим результатом значение — это просто булевский флаг успех/неудача. Для входящих пакетов в качестве ассоциированных данных могла бы выступать полезная нагрузка пакета.
Листинг 4.10.Обработка нескольких соединений в одном потоке с помощью объектов-обещаний
#include
void process_connections(connection_set& connections) {
while(!done(connections)) { ←
(1)
for (connection_iterator ←
(2)
connection = connections.begin(), end = connections.end();
connection != end;
++connection) {
if (connection->has_incoming_data()) {←
(3)
data_packet data = connection->incoming();
std::promise& p =
connection->get_promise(data.id); ←
(4)
p.set_value(data.payload);
Читать дальше