Яка різниця між Packaged_task та async


134

Працюючи з різьбовою моделлю C ++ 11, я помітив це

std::packaged_task<int(int,int)> task([](int a, int b) { return a + b; });
auto f = task.get_future();
task(2,3);
std::cout << f.get() << '\n';

і

auto f = std::async(std::launch::async, 
    [](int a, int b) { return a + b; }, 2, 3);
std::cout << f.get() << '\n';

здається, роблять точно те саме. Я розумію, що могла б бути велика різниця, якби я бігав std::asyncз ним std::launch::deferred, але чи є в цьому випадку?

Яка різниця між цими двома підходами, і що ще важливіше, у яких випадках використання я повинен використовувати один над іншим?

Відповіді:


161

Насправді наведений вами приклад показує відмінності, якщо ви використовуєте досить довгу функцію, наприклад

//! sleeps for one second and returns 1
auto sleep = [](){
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 1;
};

Пакетне завдання

packaged_taskЧи не заводиться на це самостійно, ви повинні посилатися на нього:

std::packaged_task<int()> task(sleep);

auto f = task.get_future();
task(); // invoke the function

// You have to wait until task returns. Since task calls sleep
// you will have to wait at least 1 second.
std::cout << "You can see this after 1 second\n";

// However, f.get() will be available, since task has already finished.
std::cout << f.get() << std::endl;

std::async

З іншого боку, std::asyncс launch::asyncспробує запустити завдання в іншій нитці:

auto f = std::async(std::launch::async, sleep);
std::cout << "You can see this immediately!\n";

// However, the value of the future will be available after sleep has finished
// so f.get() can block up to 1 second.
std::cout << f.get() << "This will be shown after a second!\n";

Недолік

Але перш ніж спробувати використовувати asyncдля всього, майте на увазі, що повернене майбутнє має особливий спільний стан, який вимагає future::~futureблокувати:

std::async(do_work1); // ~future blocks
std::async(do_work2); // ~future blocks

/* output: (assuming that do_work* log their progress)
    do_work1() started;
    do_work1() stopped;
    do_work2() started;
    do_work2() stopped;
*/

Тож якщо ви хочете справжнього асинхронного, вам потрібно тримати повернене future, або якщо ви не піклуєтесь про результат, якщо обставини змінюються:

{
    auto pizza = std::async(get_pizza);
    /* ... */
    if(need_to_go)
        return;          // ~future will block
    else
       eat(pizza.get());
}   

Для отримання додаткової інформації з цього питання див стаття Герба Саттер asyncі~future , який описує проблему, і Скотт Майєр std::futuresз std::asyncне є спеціальними , яка описує розуміння. Також зауважте, що така поведінка була зазначена в C ++ 14 і вище , але також зазвичай реалізована в C ++ 11.

Подальші відмінності

Використовуючи std::asyncви більше не можете виконувати завдання на певній нитці, де std::packaged_taskможна перемістити до інших потоків.

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::thread myThread(std::move(task),2,3);

std::cout << f.get() << "\n";

Також packaged_taskперед тим, як дзвонити f.get(), потрібно викликати потреби , інакше програма застигне, оскільки майбутнє ніколи не стане готовим:

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);

TL; DR

Використовуйте, std::asyncякщо ви хочете, щоб якісь речі були зроблені, і вам зовсім не важливо, коли вони будуть виконані, і std::packaged_taskякщо ви хочете завершити їх, щоб перемістити їх в інші теми або зателефонувати їм пізніше. Або, цитувати Крістіана :

Врешті-решт, std::packaged_taskце лише функція нижчого рівня для реалізації std::async(саме тому вона може зробити більше, ніж std::asyncякщо використовувати разом з іншими матеріалами нижчого рівня, як-от std::thread). Просто висловлювалася std::packaged_taskце std::functionпов'язано з std::futureі std::asyncобгорток і називає std::packaged_task(можливо , в іншому потоці).


9
Вам слід додати, що майбутнє, повернене блоками async при знищенні (як якщо б ви подзвонили get), тоді як повернене з packged_task не робить.
John5342

22
Врешті-решт, std::packaged_taskце лише функція нижчого рівня для реалізації std::async(саме тому вона може зробити більше, ніж std::asyncякщо використовувати разом з іншими матеріалами нижчого рівня, як-от std::thread). Просто висловлювалася std::packaged_taskце std::functionпов'язано з std::futureі std::asyncобгорток і називає std::packaged_task(можливо , в іншому потоці).
Крістіан Рау

Я роблю кілька експериментів над блоком ~ future (). Я не міг повторити ефект блокування на майбутнє знищення об'єкта. Все працювало асинхронно. Я використовую VS 2013, і коли я запускаю async, я використовував std :: launch :: async. Чи VC ++ якось "виправляє" це питання?
Френк Лю

1
@FrankLiu: Ну, N3451 - це прийнята пропозиція, яка (наскільки я знаю) перейшла в C ++ 14. З огляду на те, що Herb працює в Microsoft, я не здивуюся, якщо ця функція буде реалізована в VS2013. Компілятор, який суворо дотримується правил C ++ 11, все ще демонструє таку поведінку.
Зета

1
@Mikhail Ця відповідь передує як C ++ 14, так і C ++ 17, тому я не мав під рукою стандартів, а лише пропозиції. Я вилучу абзац.
Зета

1

Пакетоване завдання проти асинхронізації

p> У пакетному завданні міститься задача[function or function object]та пара майбутнього / обіцянка. Коли завдання виконує оператор повернення, він викликаєset_value(..)на цьомуpackaged_taskобіцянку «S.

a> Враховуючи завдання майбутнього, обіцянки та пакету, ми можемо створювати прості завдання, не турбуючись надто про тему [нитка - це те, що ми надаємо для виконання завдання].

Однак нам потрібно врахувати, скільки потоків використовувати або найкраще виконувати завдання на поточному потоці або на іншому і т. Д. Такі рішення можна обробляти за допомогою запуску потоку, який називається async(), який вирішує, чи створити новий потік чи переробити старий один або просто виконати завдання на поточному потоці. Це повертає майбутнє.


0

"Шаблон класу std :: Packaged_task обертає будь-яку цільову позицію (функцію, лямбда-вираз, прив'язуючий вираз чи інший об'єкт функції), щоб його можна було викликати асинхронно. через std :: майбутні об’єкти. "

"Функція async шаблону запускає функцію f асинхронно (можливо, в окремому потоці) і повертає std :: future, який врешті-решт утримуватиме результат виклику цієї функції."

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.