В чем разница между 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, могла бы быть большая разница , но есть ли она в этом случае?

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

nijansen
источник

Ответы:

161

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

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

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

Zeta
источник
9
Вы должны добавить, что будущее, возвращаемое асинхронными блоками при уничтожении (как если бы вы вызывали get), тогда как будущее, возвращаемое из packaged_task, не делает.
John5342
23
В конце концов, a 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. Учитывая, что Херб работает в Microsoft, я не удивлюсь, если эта функция будет реализована в VS2013. Компилятор, который строго следует правилам C ++ 11, все равно будет демонстрировать такое поведение.
Zeta
1
@Mikhail Этот ответ предшествует как C ++ 14, так и C ++ 17, поэтому у меня под рукой не было стандартов, а были только предложения. Я уберу абзац.
Зета
1

Пакетная задача против асинхронного

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

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

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

maxshuty
источник
0

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

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

Radoslav.B
источник