Я просто прочитал статьюФьючерсы сделаны правильно‘, и главное, чего не хватает в обещаниях 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();
Что не так с этим подходом к компоновке? Это правильный способ достижения компоновки? Нужен ли нам какой-нибудь элегантный синтаксический эдулкорант по этому шаблону?
when_any
а также when_all
являются совершенно допустимыми способами составления фьючерсов. Они оба соответствуют параллельной композиции, где составная операция ожидает одну или все составные операции.
Нам также нужна последовательная композиция (чего нет в Boost.Thread). Это может быть, например, future<T>::then
функция, которая позволяет поставить в очередь операцию, которая использует значение будущего и запускается, когда будущее готово. Это можно реализовать самостоятельно, но с компромиссом эффективности. Херб Саттер говорит об этом в своем недавнее видео Channel9.
N3428 Черновик предложения по добавлению этих функций (и других) в стандартную библиотеку C ++. Все они являются библиотечными функциями и не добавляют новый синтаксис в язык. Дополнительно, N3328 это предложение добавить синтаксис для возобновляемые функции (как использование async
/await
в C #), который будет использовать future<T>::then
внутренне.
Очки за использование слова 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 обеспечивает еще больше удобных функций.