C++ Concurrency Guide <future> (2) std::packaged_task

std::packaged_task wraps a callable object and allows asynchronous access to the results produced by the callable object. In the sense of packaging the callable object, std::packaged_task is similar to std::function, except that std::packaged_task will The execution result of the wrapped callable object is passed to a std::future object (the object usually gets the execution result of the std::packaged_task task in another thread).

The std::packaged_task object contains two most basic elements:

  1. The packaged task (stored task), the task (task) is a callable object, such as a function pointer, member function pointer or function object
  2. Shared state (shared state), used to save the return value of the task, the effect of asynchronous access to the shared state can be achieved through the std::future object

The std::future object associated with the shared state can be obtained through std::packged_task::get_future. After calling this function, the two objects share the same shared state, which is explained as follows:

  • The std::packaged_task object is an asynchronous Provider, which sets the value of the shared state by calling the packaged task at a certain moment.
  • The std::future object is an asynchronous return object, through which the value of the shared state can be obtained. Of course, it is necessary to wait for the shared state flag to become ready when necessary.

The life cycle of the shared state of std::packaged_task continues until the last object associated with it is released or destroyed. The following small example roughly talks about the usage of 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;
}

Note: The value obtained by ret.get() is the return value of the packaged_task function bound to the task

std::packaged_task constructor

default packaged_task() noexcept;
initialization template < class Fn > explicit packaged_task (Fn&& fn);
with allocator template < class Fn, class Alloc > explicit packaged_task (allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
copy [deleted] packaged_task (const packaged_task&) = delete;
move packaged_task (packaged_task&& x) noexcept;

There are 5 forms of std::packaged_task constructor, but copy construction has been disabled. The following briefly introduces the semantics of the above-mentioned constructors:

  1. The default constructor initializes an empty shared state, and the packaged_task object has no packaged tasks.
  2. Initialize a shared state, and the packaged task is specified by the parameter fn.
  3. The constructor with a custom memory allocator is similar to the default constructor, but uses a custom allocator to allocate shared state.
  4. The copy constructor is disabled.
  5. Move constructor.

The following examples introduce the usage of various constructors:

#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;
}

Similar to std::promise, std::packaged_task also disables ordinary assignment operations and only allows move assignment operations.

std::packaged_task::valid 介绍

Check whether the current packaged_task is associated with a valid shared state. For the packaged_task object generated by the default constructor, the function returns false, unless a move assignment operation or a swap operation is performed in the middle.

Consider the following example:

#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 介绍

Returns a future object related to the shared state of the packaged_task object. The returned future object can obtain a certain value or exception set by another thread on the shared state of the packaged_task object.

Please see the example:

#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) 介绍

Call the object packaged by the packaged_task object (usually a function pointer, function object, lambda expression, etc.), and the parameter passed in is args. Two things usually happen when calling this function:

  1. If the object packaged by packaged_task is successfully called, the return value (if the packaged object has a return value) is saved in the shared state of packaged_task.
  2. If the object packaged by packaged_task fails to be called and an exception is thrown, the exception will also be saved in the shared state of packaged_task.

In the above two cases, the flag of the shared state becomes ready, so other threads waiting for the shared state can obtain the value of the shared state or an exception and continue execution.

The value of the shared state can be obtained by calling get on the future object (obtained by get_future).

Since the packaged task is specified when the packaged_task is constructed, the effect of calling operator() is determined by the callable object specified when the packaged_task object is constructed:

  • If the packaged task is a function pointer or function object, calling std::packaged_task::operator() just passes the parameters to the packaged object.
  • If the packaged task is a pointer to a non-static member function of the class, then the first parameter of std::packaged_task::operator() should be specified as the object on which the member function is called, and the remaining parameters are used as the member function’s parameter.
  • If the packaged task is a non-static member variable that points to the class, then std::packaged_task::operator() only allows a single parameter.

std::packaged_task::make_ready_at_thread_exit 介绍

This function will call the packaged task and pass parameters to the task, similar to the operator() member function of std::packaged_task. But unlike the operator() function, make_ready_at_thread_exit does not immediately set the shared state flag to ready, but sets the shared state flag when the thread exits.

If the future object associated with the packaged_task shared state is waiting at future::get, the current future::get call will be blocked until the thread exits. Once the thread exits, the future::get call continues to execute, or an exception is thrown.

Note that this function has already set the value of the promise shared state. If there are other operations to set or modify the value of the shared state before the thread ends, future_error( promise_already_satisfied) will be thrown.

std::packaged_task::reset() introduction

Reset the shared state of packaged_task, but keep the previous packaged task.
Please see the example, in this example, packaged_task is reused many times:

#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() 介绍

Exchange the shared state of packaged_task.

Guess you like

Origin blog.csdn.net/qq_24649627/article/details/114141454