Но как насчет случая, когда надо ждать всех результатов разного типа?
Мы можем представить API, который, честно говоря, довольно крутой. Рассмотрим структуру выборки:
struct Data {
std::string str;
double inexact;
int exact;
friend std::ostream& operator<<( std::ostream& out, Data const& data);
...
};
Давайте заполним его членов из функций задач, работающих одновременно:
Data data = wait_all_members< Data >(
[](){ return sleeper("wams_left", 100); },
[](){ return sleeper(3.14, 150); },
[](){ return sleeper(17, 50); });
std::cout << "wait_all_members<Data>(success) => " << data << std::endl;
Заметим, что для этого случая мы сначала отказываемся от понятия захвата самого раннего результата, и так далее: мы должны заполнить именно пройденную структуру в порядке слева направо.
Это позволяет реализовать очень просто:
template< typename Result, typename ... Fns >
Result wait_all_members( Fns && ... functions) {
return wait_all_members_get< Result >(
boost::fibers::async( std::forward< Fns >( functions) ) ... );
}
template< typename Result, typename ... Futures >
Result wait_all_members_get( Futures && ... futures) {
return Result{ futures.get() ... };
}
Заманчиво попытаться реализовать wait_all_members()
в качестве одного строки, как это:
return Result{ boost::fibers::async(functions).get()... };
Проблема с этой тактикой заключается в том, что она будет сериализовать все функции задачи. Среда выполнения делает один проход через функции
, вызывая волокна::async()
для каждого, а затем сразу же вызывая будущее::get()
на его возвращенном будущее<>
. Это блокирует скрытую петлю. Вышесказанное почти эквивалентно написанию:
return Result{ functions()... };
в котором, конечно, нет никакой параллели.
Пропуск пакета аргументов через границу вызова функции (wait_all_members_get()
) вынуждает время выполнения совершать два прохода: один в wait_all_members()
для сбора future<>
из всех вызовов async()
, второй в wait_all_members_get()
для получения каждого из результатов.
Как отмечается в комментариях, в пределах wait_all_members_get()
параметр расширения пакета становится неактуальным поведение блокировки get()
. По пути мы нажмем get()
для самой медленной функции задачи; после этого каждый последующий get()
завершится в тривиальное время.
Кстати, мы также можем использовать этот же API для заполнения вектора или другой коллекции:
auto strings = wait_all_members< std::vector< std::string > >(
[](){ return sleeper("wamv_left", 150); },
[](){ return sleeper("wamv_middle", 100); },
[](){ return sleeper("wamv_right", 50); });
std::cout << "wait_all_members<vector>() =>";
for ( std::string const& str : strings) {
std::cout << " '" << str << "'";
}
std::cout << std::endl;