4.2.1. Возврат значения из фоновой задачи

Допустим, вы начали какое-то длительное вычисление, которое в конечном итоге должно дать полезный результат, но пока без него можно обойтись. Быть может, вы нашли способ получить ответ на «Главный возрос жизни, Вселенной и всего на свете» из книги Дугласа Адамса[7]. Для вычисления можно запустить новый поток, но придётся самостоятельно позаботиться о передаче в основную программу результата, потому что в классе std::thread такой механизм не предусмотрен. Тут-то и приходит на помощь шаблон функции std::async (также объявленный в заголовке <future>).

Функция std::async позволяет запустить асинхронную задачу, результат которой прямо сейчас не нужен. Но вместо объекта std::thread она возвращает объект std::future, который будет содержать возвращенное значение, когда оно станет доступно. Когда программе понадобится значение, она вызовет функцию-член get() объекта-будущего, и тогда поток будет приостановлен до готовности будущего результата, после чего вернет значение. В листинге ниже оказан простой пример.

Листинг 4.6. Использование std::future для получения результата асинхронной задачи

#include <future>

#include <iostream>

int find_the_answer_to_ltuae();

void do_other_stuff();

int main() {

 std::future<int> the_answer =

  std::async(find_the_answer_to_ltuae);

 do_other_stuff();

 std::cout << "Ответ равен " << the_answer.get() << std::endl;

}

Шаблон std::async позволяет передать функции дополнительные параметры, точно так же, как std::thread. Если первым аргументом является указатель на функцию-член, то второй аргумент должен содержать объект, от имени которого эта функция-член вызывается (сам объект, указатель на него или обертывающий его std::ref), а все последующие аргументы передаются без изменения функции-члену. В противном случае второй и последующие аргументы передаются функции или допускающему вызов объекту, заданному в первом аргументе. Как и в std::thread, если аргументы представляют собой r-значения, то создаются их копии посредством перемещения оригинала. Это позволяет использовать в качестве объекта-функции и аргументов типы, допускающие только перемещение. Пример см. в листинге ниже.

Листинг 4.7. Передача аргументов функции, заданной в std::async

#include <string>

#include <future>

struct X {

 void foo(int, std::string const&);

 std::string bar(std::string const&);

};

                                                │ Вызывается

X x;                                            │ p->foo(42,"hello"),

auto f1 = std::async(&X::foo, &x, 42, "hello");←┘ где p=&x

auto f2 = std::async(&X::bar, x, "goodbye");←┐ вызывается

                                             │ tmpx.bar("goodbye"),

struct Y {                                   │ где tmpx — копия x

 double operator()(double);

};                               │ Вызывается tmpy(3.141),

                                 │ где tmpy создается

Y y;                             │ из Y перемещающим

auto f3 = std::async(Y(), 3.141)←┘ конструктором

auto f4 = std::async(std::ref(y), 2.718);← Вызывается y(2.718)

X baz(X&);

std::async(baz, std::ref(x); ← Вызывается baz(x)

class move_only {

public:

 move_only();

 move_only(move_only&&);

 move_only(move_only const&) = delete;

 move_only& operator=(move_only&&);

 move_only& operator=(move_only const&) = delete;

 void operator()();                │ Вызывается tmp(), где tmp

};                                 │ конструируется с помощью

auto f5 = std::async(move_only());←┘ std::move(move_only())

По умолчанию реализации предоставлено право решать, запускает ли std::async новый поток или задача работает синхронно, когда программа ожидает будущего результата. В большинстве случаев такое поведение вас устроит, но можно задать требуемый режим в дополнительном параметре std::async перед вызываемой функцией. Этот параметр имеет тип std::launch и может принимать следующие значения: std::launch::deferred — отложить вызов функции до того момента, когда будет вызвана функция-член wait() или get() объекта-будущего; std::launch::async — запускать функцию в отдельном потоке; std::launch::deferred | std::launch::async — оставить решение на усмотрение реализации. Последний вариант подразумевается по умолчанию. В случае отложенного вызова функция может вообще никогда не выполниться. Например:

auto f6 =                                  │ Выполнять в

 std::async(std::launch::async, Y(), 1.2);←┘ новом потоке

auto f7 =

 std::async(

  std::launch::deferred, baz, std::ref(x)); ←┐

auto f8 = std::async(                      ←┐│ Выполнять

 std::launch::deferred | std::launch::async,││ при вызове

 baz, std::ref(x));                         ││ wait() или get()

auto f9 = std::async(baz, std::ref(x));    ←┼ Оставить на

                                            │ усмотрение реализации

f7.wait();← Вызвать отложенную функцию

Ниже в этой главе и далее в главе 8 мы увидим, что с помощью std::async легко разбивать алгоритм на параллельно выполняемые задачи. Однако это не единственный способ ассоциировать объект std::future с задачей; можно также обернуть задачу объектом шаблонного класса std::packaged_task<> или написать код, который будет явно устанавливать значения с помощью шаблонного класса std::promise<>. Шаблон std::packaged_task является абстракцией более высокого уровня, чем std::promise, поэтому начнем с него.