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

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

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

99

Решение

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

//! 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 (возможно, в другой теме).

129

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

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

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

1

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

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

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

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

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