Руководство по параллелизму C ++ <будущее> (2) std :: packaged_task

std :: packaged_task обертывает вызываемый объект и обеспечивает асинхронный доступ к результатам, созданным вызываемым объектом. В смысле упаковки вызываемого объекта std :: packaged_task аналогичен std :: function, за исключением того, что std :: packaged_task будет результат выполнения обернутого вызываемого объекта передается объекту std :: future (объект обычно получает результат выполнения задачи std :: packaged_task в другом потоке).

Объект std :: packaged_task содержит два основных элемента:

  1. Упакованная задача (сохраненная задача), задача (задача) - это вызываемый объект, например указатель функции, указатель функции-члена или объект функции
  2. Общее состояние (shared state), используемое для сохранения возвращаемого значения задачи, вы можете использовать объект std :: future для достижения эффекта асинхронного доступа к общему состоянию

Объект std :: future, связанный с общим состоянием, можно получить с помощью std :: packged_task :: get_future. После вызова этой функции два объекта разделяют одно и то же общее состояние, что объясняется следующим образом:

  • Объект std :: packaged_task - это асинхронный провайдер, который устанавливает значение общего состояния, вызывая упакованную задачу в определенный момент.
  • Объект std :: future - это асинхронный объект возврата, с помощью которого можно получить значение общего состояния.Конечно, при необходимости необходимо дождаться готовности флага общего состояния.

Жизненный цикл общего состояния std :: packaged_task продолжается до тех пор, пока последний связанный с ним объект не будет освобожден или уничтожен. В следующем небольшом примере примерно рассказывается об использовании std :: packaged_task:

#include <iostream>     // std::cout
#include <future>       // std::packaged_task, std::future
#include <chrono>       // std::chrono::seconds
#include <thread>       // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
    
    
    for (int i=from; i!=to; --i) {
    
    
        std::cout << i << '\n';
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    std::cout << "Finished!\n";
    return from - to;
}

int main ()
{
    
    
    std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
    std::future<int> ret = task.get_future();         // 获得与 packaged_task 共享状态相关联的 future 对象.

    std::thread th(std::move(task), 10, 0);           // 创建一个新线程完成计数任务.

    int value = ret.get();                            // 等待任务完成并获取结果.

    std::cout << "The countdown lasted for " << value << " seconds.\n";

    th.join();
    
    return 0;
}

Примечание. Значение, полученное функцией ret.get (), является возвращаемым значением функции packaged_task, привязанной к задаче.

std :: packaged_task конструктор

дефолт packaged_task () noexcept;
инициализация template <class Fn> явный packaged_task (Fn && fn);
с распределителем template <class Fn, class Alloc> явный packaged_task (allocator_arg_t aa, const Alloc & alloc, Fn && fn);
копировать [удалено] packaged_task (const packaged_task &) = удалить;
переехать упакованная_задача (packaged_task && x) noexcept;

Существует 5 форм конструктора std :: packaged_task, но построение копии отключено. Ниже кратко представлена ​​семантика вышеупомянутых конструкторов:

  1. Конструктор по умолчанию инициализирует пустое общее состояние, а объект packaged_task не имеет упакованных задач.
  2. Инициализируйте общее состояние, и упакованная задача указывается параметром fn.
  3. Конструктор с настраиваемым распределителем памяти похож на конструктор по умолчанию, но использует настраиваемый распределитель для выделения общего состояния.
  4. Конструктор копирования отключен.
  5. Конструктор перемещения.

Следующие примеры знакомят с использованием различных конструкторов:

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    
    
    std::packaged_task<int(int)> foo; // 默认构造函数.

    // 使用 lambda 表达式初始化一个 packaged_task 对象.
    std::packaged_task<int(int)> bar([](int x){
    
    return x*2;});

    foo = std::move(bar); // move-赋值操作,也是 C++11 中的新特性.

    // 获取与 packaged_task 共享状态相关联的 future 对象.
    std::future<int> ret = foo.get_future();

    std::thread(std::move(foo), 10).detach(); // 产生线程,调用被包装的任务.

    int value = ret.get(); // 等待任务完成并获取结果.
    std::cout << "The double of 10 is " << value << ".\n";

    return 0;
}

Подобно std :: prom, std :: packaged_task также отключает обычные операции присваивания и разрешает только операции присваивания перемещения.

std :: packaged_task :: действительный 介绍

Проверьте, связана ли текущая packaged_task с допустимым общим состоянием.Для объекта packaged_task, созданного конструктором по умолчанию, функция возвращает false, если только в середине не выполняется операция присваивания перемещения или операция подкачки.

Рассмотрим следующий пример:

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

// 在新线程中启动一个 int(int) packaged_task.
std::future<int> launcher(std::packaged_task<int(int)>& tsk, int arg)
{
    
    
    if (tsk.valid()) {
    
    
        std::future<int> ret = tsk.get_future();
        std::thread (std::move(tsk),arg).detach();
        return ret;
    }
    else 
        return std::future<int>();
}

int main ()
{
    
    
    std::packaged_task<int(int)> tsk([](int x){
    
    return x*2;});

    std::future<int> fut = launcher(tsk,25);

    std::cout << "The double of 25 is " << fut.get() << ".\n";

    return 0;
}

std :: packaged_task :: get_future 介绍

Возвращает будущий объект, связанный с общим состоянием объекта packaged_task. Возвращенный будущий объект может получить определенное значение или исключение, установленное другим потоком в общем состоянии объекта packaged_task.

См. Пример:

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main ()
{
    
    
    std::packaged_task<int(int)> tsk([](int x) {
    
     return x * 3; }); // package task

    std::future<int> fut = tsk.get_future();     // 获取 future 对象.

    std::thread(std::move(tsk), 100).detach();   // 生成新线程并调用packaged_task.

    int value = fut.get();                       // 等待任务完成, 并获取结果.

    std::cout << "The triple of 100 is " << value << ".\n";

    return 0;
}

std :: packaged_task :: operator () (Args… args) 介绍

Вызовите объект, упакованный объектом packaged_task (обычно указатель на функцию, объект функции, лямбда-выражение и т. Д.), И переданный параметр - args. При вызове этой функции обычно происходят две вещи:

  1. Если объект, упакованный с помощью packaged_task, успешно вызван, возвращаемое значение (если у упакованного объекта есть возвращаемое значение) сохраняется в общем состоянии packaged_task.
  2. Если объект, упакованный packaged_task, не может быть вызван и возникает исключение, исключение также будет сохранено в общем состоянии packaged_task.

В двух вышеупомянутых случаях флаг общего состояния становится готовым, поэтому другие потоки, ожидающие общего состояния, могут получить значение общего состояния или исключения и продолжить выполнение.

Значение общего состояния можно получить, вызвав get для будущего объекта (полученного с помощью get_future).

Поскольку упакованная задача указывается при создании packaged_task, эффект вызова operator () определяется вызываемым объектом, указанным при создании объекта packaged_task:

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

std :: packaged_task :: make_ready_at_thread_exit 介绍

Эта функция вызовет упакованную задачу и передаст ей параметры, аналогично функции-члену operator () для std :: packaged_task. Но в отличие от функции operator () make_ready_at_thread_exit не сразу устанавливает флаг общего состояния в состояние готовности, а устанавливает флаг общего состояния при выходе из потока.

Если объект future, связанный с общим состоянием packaged_task, ожидает в future :: get, текущий вызов future :: get будет заблокирован до выхода из потока. После выхода из потока вызов future :: get продолжает выполняться или генерируется исключение.

Обратите внимание, что эта функция уже установила значение общего состояния обещания. Если есть другие операции для установки или изменения значения общего состояния до завершения потока, будет выброшено future_error (обещание_already_satisfied).

std :: packaged_task :: reset () введение

Сбросьте общее состояние packaged_task, но сохраните предыдущую упакованную задачу.
Пожалуйста, посмотрите пример, в этом примере packaged_task используется много раз:

#include <iostream>
#include <cmath>
#include <thread>
#include <future>
 
int main()
{
    
    
    std::packaged_task<int(int,int)> task([](int a, int b) {
    
    
        return std::pow(a, b);
    });
    std::future<int> result = task.get_future();
    task(2, 9);
    std::cout << "2^9 = " << result.get() << '\n';
 
    task.reset();
    result = task.get_future();
    std::thread task_td(std::move(task), 2, 10);
    task_td.join();
    std::cout << "2^10 = " << result.get() << '\n';
}

std :: packaged_task :: swap () 介绍

Обменять общее состояние packaged_task.

рекомендация

отblog.csdn.net/qq_24649627/article/details/114141454