будущая компоновка и повышение :: wait_for_all

Я просто прочитал статьюФьючерсы сделаны правильно‘, и главное, чего не хватает в обещаниях c ++ 11, это то, что создание составных фьючерсов из существующих

Я сейчас смотрю на документацию повышение :: wait_for_any

но рассмотрим следующий пример:

int calculate_the_answer_to_life_the_universe_and_everything()
{
return 42;
}

int calculate_the_answer_to_death_and_anything_in_between()
{
return 121;
}

boost::packaged_task<int> pt(calculate_the_answer_to_life_the_universe_and_everything);
boost:: future<int> fi=pt.get_future();
boost::packaged_task<int> pt2(calculate_the_answer_to_death_and_anything_in_between);
boost:: future<int> fi2=pt2.get_future();

....int calculate_the_oscillation_of_barzoom(boost::future<int>& a, boost::future<int>& b)
{
boost::wait_for_all(a,b);
return a.get() + b.get();
}

boost::packaged_task<int> pt_composite(boost::bind(calculate_the_oscillation_of_barzoom, fi , fi2));
boost:: future<int> fi_composite=pt_composite.get_future();

Что не так с этим подходом к компоновке? Это правильный способ достижения компоновки? Нужен ли нам какой-нибудь элегантный синтаксический эдулкорант по этому шаблону?

6

Решение

when_any а также when_all являются совершенно допустимыми способами составления фьючерсов. Они оба соответствуют параллельной композиции, где составная операция ожидает одну или все составные операции.

Нам также нужна последовательная композиция (чего нет в Boost.Thread). Это может быть, например, future<T>::then функция, которая позволяет поставить в очередь операцию, которая использует значение будущего и запускается, когда будущее готово. Это можно реализовать самостоятельно, но с компромиссом эффективности. Херб Саттер говорит об этом в своем недавнее видео Channel9.

N3428 Черновик предложения по добавлению этих функций (и других) в стандартную библиотеку C ++. Все они являются библиотечными функциями и не добавляют новый синтаксис в язык. Дополнительно, N3328 это предложение добавить синтаксис для возобновляемые функции (как использование async/await в C #), который будет использовать future<T>::then внутренне.

5

Другие решения

Очки за использование слова edulcorant. 🙂

Проблема с вашим примером кода состоит в том, что вы упаковываете все в задачи, но вы никогда график эти задачи для исполнения!

int calculate_the_answer_to_life() { ... }

int calculate_the_answer_to_death() { ... }

std::packaged_task<int()> pt(calculate_the_answer_to_life);
std::future<int> fi = pt.get_future();
std::packaged_task<int()> pt2(calculate_the_answer_to_death);
std::future<int> fi2 = pt2.get_future();

int calculate_barzoom(std::future<int>& a, std::future<int>& b)
{
boost::wait_for_all(a, b);
return a.get() + b.get();
}

std::packaged_task<int()> pt_composite([]{ return calculate_barzoom(fi, fi2); });
std::future<int> fi_composite = pt_composite.get_future();

Если в этот момент я напишу

pt_composite();
int result = fi_composite.get();

моя программа будет заблокирована навсегда. Это никогда не завершится, потому что pt_composite заблокирован на calculate_barzoom, который заблокирован на wait_for_all, который заблокирован на обоих fi а также fi2, ни один из которых никогда не завершится, пока кто-нибудь не выполнит pt или же pt2 соответственно. И никто никогда не выполнит их, потому что моя программа заблокирована!

Вы, вероятно, хотели, чтобы я написал что-то вроде этого:

std::async(pt);
std::async(pt2);
std::async(pt_composite);
int result = fi_composite.get();

это буду работать. Но это крайне неэффективно — мы порождаем три рабочих потока (через три вызова async), чтобы выполнить работу двух потоков. Этот третий поток — тот, который работает pt_composite — появится сразу, а потом просто сидеть там спать до тех пор pt а также pt2 закончили бег. Это лучше чем спиннинг, но это значительно хуже, чем не существует: это означает, что у нашего пула потоков на одного рабочего меньше, чем должно быть. В правдоподобной реализации пула потоков только с одним потоком на ядро ​​ЦП и большим количеством задач, приходящих постоянно, это означает, что у нас одно ЦП просто бездействует, потому что рабочий поток, который был означало работать на этом ядре в настоящее время заблокирован внутри wait_for_all,

Что мы хочу сделать это объявить наши намерения декларативно:

int calculate_the_answer_to_life() { ... }

int calculate_the_answer_to_death() { ... }

std::future<int> fi = std::async(calculate_the_answer_to_life);
std::future<int> fi2 = std::async(calculate_the_answer_to_death);
std::future<int> fi_composite = std::when_all(fi, fi2).then([](auto a, auto b) {
assert(a.is_ready() && b.is_ready());
return a.get() + b.get();
});

int result = fi_composite.get();

и затем заставить библиотеку и планировщик работать вместе, чтобы сделать правильную вещь: не создавать ни одного рабочего потока, который не может немедленно приступить к выполнению своей задачи. Если конечный пользователь должен написать хотя бы одну строку кода, которая явно спит, ждет или блокирует, определенная производительность определенно теряется.

Другими словами: Инициировать не рабочий поток до своего времени.


Очевидно, это возможный делать все это в стандартном C ++, без поддержки библиотеки; вот так реализована сама библиотека! Но это огромная боль для реализации с нуля, со многими тонкими ловушками; вот почему хорошо, что поддержка библиотек скоро появится.

Предложение ИСО N3428 упоминается в Ответ Рошана Шарифа был обновлен как N3857, а также N3865 обеспечивает еще больше удобных функций.

3

По вопросам рекламы [email protected]