Когда следует использовать std::promise
над std::async
или std::packaged_task
?
Можете ли вы дать мне практические примеры того, когда использовать каждый из них?
Когда использовать обещание через async или packaged_task?
Ответ 1
станд :: асинхронной
std::async
- удобный и простой способ получить std::future
, но:
-
Не всегда начинается новый поток; передайте
std::launch::async
в качестве первого параметра, чтобы вызвать его.auto f = std::async( std::launch::async, func );
-
Деструктор
std::~future
может блокироваться до завершения нового потокаauto sleep = [](int s) { std::this_thread::sleep_for(std::chrono::seconds(s)); }; { auto f = std::async( std::launch::async, sleep, 5 ); }
Обычно мы ожидаем, что
.get()
только.get()
или.wait()
, но дляstd::future
возвращенного изstd::async
, деструктор также может блокироваться, поэтому будьте осторожны, чтобы не блокировать ваш основной поток, просто забыв об этом, -
Если
std::future
хранится во временном объекте, вызовstd::async
будет немедленно заблокирован, поэтому следующий блок займет 10 секунд, если вы удалитеauto f =
initializations:auto sleep = [](int s) { std::this_thread::sleep_for(std::chrono::seconds(s)); }; { auto f1 = std::async( std::launch::async, sleep, 5 ); auto f2 = std::async( std::launch::async, sleep, 5 ); }
станд :: packaged_task
Сам по себе std::packaged_task
не имеет ничего общего с потоками: это просто функтор и связанный с ним std::future
. Учтите следующее:
auto task = [](int i) { std::this_thread::sleep_for(std::chrono::seconds(5)); return i+100; };
std::packaged_task< int(int) > package{ task };
std::future<int> f = package.get_future();
package(1);
std::cout << f.get() << "\n";
Здесь мы просто запускаем задачу с помощью package(1)
, и после ее возвращения f
готово, поэтому блокировка для .get()
не .get()
.
В std::packaged_task
есть функция, которая делает его очень полезным для потоков. Вместо простой функции вы можете инициализировать std::thread
с помощью std::packaged_task
который дает действительно хороший способ добраться до 'std :: future'. Учтите следующее:
std::packaged_task< int(int) > package{ task };
std::future<int> f = package.get_future();
std::thread t { std::move(package), 5 };
std::cout << f.get() << "\n"; //block here until t finishes
t.join();
Поскольку std::packaged_task
packaged_task не может быть скопирован, вы должны переместить его в новый поток с помощью std::move
.
станд :: обещание
std::promise
мощный механизм. Например, вы можете передать значение в новый поток без необходимости какой-либо дополнительной синхронизации.
auto task = [](std::future<int> i) {
std::cout << i.get() << std::flush;
};
std::promise<int> p;
std::thread t{ task, p.get_future() };
std::this_thread::sleep_for(std::chrono::seconds(5));
p.set_value(5);
t.join();
Новая тема будет ждать нас на .get()
Итак, в общем, отвечая на ваш вопрос:
- Используйте
std::async
только для простых вещей, например, чтобы сделать некоторые вызовы неблокирующими, но имейте в виду комментарии о блокировке выше. -
Используйте
std::packaged_task
чтобы легко добраться доstd::future
и запустить его как отдельный потокstd::thread{ std::move(package), param }.detach();
или же
std::thread t { std::move(package), param };
-
Используйте
std::promise
когда вам нужно больше контроля над будущим.
Смотрите также std::shared_future
и о передаче исключений между потоками std::promise::set_exception
Ответ 2
Обещание используется для хранения значения, которое было рассчитано с использованием, например, std:: async. См. http://en.cppreference.com/w/cpp/thread/promise
Я могу представить, что вы задаетесь вопросом о различии между std:: packaged_task и std:: async (в самом общем подходе, std:: async запускает отдельный поток NOW для запуска функции /lambda/etc с (вероятным) дорогостоящим расчет. Std:: packaged_task используется для переноса функции /lambda/etc с текущими значениями аргументов, чтобы вы могли LATER запускать ее либо синхронно, либо в отдельном потоке).
Оба std:: packaged_task и std:: async предоставляют std:: future, которые будут содержать РЕЗУЛЬТАТ завершенной функции /lambda/etc после запуска. Внутри, std:: future использует std:: prom для хранения этого результата.