Подтвердить что ты не робот

Когда использовать обещание через async или packaged_task?

Когда следует использовать std::promise над std::async или std::packaged_task? Можете ли вы дать мне практические примеры того, когда использовать каждый из них?

4b9b3361

Ответ 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 для хранения этого результата.