В чем разница между packaged_task и async

Во время работы с проточной моделью С++ 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, но есть ли в этом случае?

В чем разница между этими двумя подходами и, что более важно, в каких вариантах использования я должен использовать один над другим?

Ответ 1

Фактически приведенный вами пример показывает различия, если вы используете довольно длинную функцию, например

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

Упакованная задача

A 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";

Недостаток (только для С++ 11)

Но прежде чем пытаться использовать 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());
}   

Для получения дополнительной информации об этом см. статью Herb Sutter async и ~future, в которой описывается проблема, а Скотт Мейер std::futures from std::async не являются специальными, в которых описываются идеи. Также обратите внимание, что это поведение специфично для С++ 11, в С++ 14 этот деструктор будет не блокировать в вызывающем потоке.

Дополнительные отличия

Используя 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";

Кроме того, перед вызовом f.get() необходимо вызвать packaged_task, иначе программа будет зависеть, поскольку будущее не будет готово:

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, если вы хотите обернуть вещи, чтобы переместить их в другие потоки или вызвать их позже. Или, чтобы процитировать христианский:

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

Ответ 2

"Шаблон класса std:: packaged_task обертывает любую вызываемую цель (функция, лямбда-выражение, выражение связывания или другая функция объект), так что он может быть вызван асинхронно. Его возвращаемое значение или исключение выбрано в общем состоянии, к которому можно получить доступ. через std:: будущие объекты."

"Функция async шаблона запускает функцию f асинхронно (потенциально в отдельном потоке) и возвращает std:: future, который будет в конечном итоге удерживать результат вызова этой функции."

Ответ 3

Packaged Task vs async

p > В пакетной задаче выполняется задача [function or function object] и пара будущего/обещания. Когда задача выполняет оператор return, она вызывает set_value(..) в обещании packaged_task.

a > Задача Будущая, обещающая и пакетная задача, мы можем создавать простые задачи, не слишком беспокоясь о потоках. [thread - это просто то, что мы даем для запуска задачи).

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