C++ multithreading: std::promise

concept

std::promiseis a template class: template<typename ResultType> class promise. Its generic parameter ResultTypeis std::promisethe type of the value held by the object std::promise<int>, which ResultTypecan be of type void, for example. std::promiseType templates provide methods to set an asynchronous result so that other threads can std::futureread the result through the instance. std::promiseAnd std::futurecooperation together to achieve multi-thread communication.

When a std::promise object is constructed, the object is associated with the new shared state. This shared state can be associated with an object by calling std::promisea function. After the call , both objects share the same shared state: the std::promise object is an asynchronous provider and should set a value for the shared state at some point. The object is an asynchronous return object that can retrieve the value of the shared state and wait for it to be ready if necessary. It should be noted that it can only be called once, and multiple calls will throw an exception. In fact , the function will change the state of std::promise to ready, and when it is called again, it will find that the state is already red, and throw an exception.get_futurestd::futureget_futurestd::futureset_valuestd::future_errorstd::promise::set_xxx

std::promiseInstances can MoveConstructible(move construction) and MoveAssignable(move assignment), but not CopyConstructible(copy construction) and CopyAssignable(copy assignment).

type definition

template<typename ResultType>
class promise
{
public:
  promise();
  promise(promise&&) noexcept;
  ~promise();
  promise& operator=(promise&&) noexcept;

  template<typename Allocator>
  promise(std::allocator_arg_t, Allocator const&);

  promise(promise const&) = delete;
  promise& operator=(promise const&) = delete;

  void swap(promise& ) noexcept;
  
  std::future<ResultType> get_future();

  void set_value(see description);
  void set_exception(std::exception_ptr p);
};
复制代码

default constructor

通过访问新的空共享状态来构造一个std::promise对象(The object is initialized with access to a new empty shared state)。并且使用ResultType类型的相关异步结果来构造std::promise实例,不过异步结果并未就绪。当没有足够内存为异步结果进行分配,那么将抛出std::bad_alloc型异常。

带分配器的构造函数

构造一个std::promise对象,使用提供的分配器来为相关异步结果分配内存。

template<typename Allocator>
promise(std::allocator_arg_t, Allocator const& alloc);
复制代码

移动构造函数

promise(promise&& other) noexcept;
复制代码

通过另一个已存在对象,构造一个std::promise对象。将已存在对象中的共享状态以及相关异步结果的所有权转移到新创建的std::promise对象当中。之后,other将无关联异步结果。

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <future>         // std::promise, std::future
#include <chrono>

std::promise<int> prom;

void print_global_promise () {
    std::future<int> fut = prom.get_future();
    int x = fut.get();
    std::cout << "value: " << x << '\n';  // value is: 20
}

int main ()
{
    std::thread th1(print_global_promise);
	// 等待线程th1线跑获调用prom.get_future()
    std::this_thread::sleep_for(std::chrono::seconds(1));
    // 将prom所有权将转移到新创建的对象prom2上
    std::promise<int> prom2(std::move(prom));
	// 对prom2进行设值,在之前的prom的get也能获取到,最终输出20
    prom2.set_value (20);
    th1.join();
    return 0;
}
复制代码

移动赋值操作符

promise& operator=(promise&& other) noexcept;
复制代码

在两个std::promise实例中转移异步结果的所有权。在other和*this之间进行异步结果所有权的转移。当*this已经有关联的异步结果,那么该异步结果的状态将会为就绪态,且伴随一个std::future_error类型异常,错误码为std::future_errc::broken_promise

将other中关联的异步结果转移到*this当中后。other中将无关联异步结果。返回*this

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

std::promise<int> prom;

void print_global_promise () {
    std::future<int> fut = prom.get_future();
    int x = fut.get();
    std::cout << "value: " << x << '\n';
}

int main () {
    std::thread th1(print_global_promise);
    prom.set_value(10);
    th1.join();

    prom = std::promise<int>();    // prom 被move赋值为一个新的 promise 对象.

    std::thread th2 (print_global_promise);
    prom.set_value (20);
    th2.join();

    return 0;
}
复制代码

swap成员函数

将两个std::promise实例中的关联异步结果进行交换。

void swap(promise& other);
复制代码

交换other和*this当中的关联异步结果。当swap使用other时,other中的异步结果就会与*this中关联异步结果相交换。二者返回来亦然。

析构函数

放弃(abandon)共享状态并销毁std::promise对象。当*this具有关联的异步结果,并且结果中没有存储值或异常(从未调用set_xx函数),那么结果将会置为就绪,伴随一个std::future_error异常,错误码为std::future_errc::broken_promise。可以看下面单独章节说明例子。

get_future成员函数

返回一个与promise对象的共享状态关联的std::future对象。

std::future<ResultType> get_future();
复制代码

首先,*this要具有关联异步结果。最后返回与*this关联异步结果关联的std::future实例。

std::future已经通过get_future()获取过了,第二次获取将会抛出一个std::future_error类型异常,伴随的错误码为std::future_errc::future_already_retrieved(Only one future object can be retrieved for each promise shared state)。 调用此函数后,promise应在某个时候使其共享状态准备就绪(通过设置值或异常),否则将在销毁时自动准备就绪并包含一个std::future_error类型的异常。

#include <iostream>       // std::cout
#include <future>         // std::promise, std::future


int main () {
    std::promise<int> prom; // 生成一个 std::promise<void> 对象.
    std::future<int> fut = prom.get_future(); // 和 future 关联.
    try {
        std::future<int> fut2 = prom.get_future(); // 多次与 future 关联.
    } catch (std::exception &e) {
        std::cout << "exception: "  << e.what() << '\n'; 
    }

    return 0;
}
复制代码

输出:

exception: std::future_error: Future already retrieved
复制代码

set_value成员函数

存储一个值到与*this关联的异步结果中。

void promise<void>::set_value();
void promise<R&>::set_value(R& r);
void promise<R>::set_value(R const& r);
void promise<R>::set_value(R&& r);
复制代码

首先,*this要具有关联异步结果。最后,当ResultType不是void型,就存储r到*this相关的异步结果当中。*this相关的异步结果的状态为就绪,且将值存入。任意等待异步结果的阻塞线程将解除阻塞。

如果异步结果已经存有一个值或一个异常,那么再次调用该函数将抛出std::future_error型异常,伴随错误码为std::future_errc::promise_already_satisfied

#include <iostream>       // std::cout
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

void print_int(std::future<int>& fut) {
    int x = fut.get(); // 获取共享状态的值.
    std::cout << "value: " << x << '\n'; // 打印 value: 10.
}

int main () {
    std::promise<int> prom; // 生成一个 std::promise<int> 对象.
    std::future<int> fut = prom.get_future(); // 和 future 关联.
    std::thread t(print_int, std::ref(fut)); // 将 future 交给另外一个线程t.
    prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步.

    try {
        prom.set_value(20); // 多次set_value.
    } catch (std::exception &e) {
        std::cout << "exception: "  << e.what() << '\n'; 
    }

    t.join();
    return 0;
}
复制代码

输出:

value: 10
exception: std::future_error: Promise already satisfied
复制代码

set_value_at_thread_exit 成员函数

存储一个值到与*this关联的异步结果中,标记异步结果为“已存储值”,但未就绪,直到线程退出时,异步结果的状态才会被设置为就绪 (Stores the exception pointer p in the shared state without making it ready immediately. Instead, it will be made ready automatically at thread exit, once all objects of thread storage duration have been destroyed)。

void promise<void>::set_value_at_thread_exit();
void promise<R&>::set_value_at_thread_exit(R& r);
void promise<R>::set_value_at_thread_exit(R const& r);
void promise<R>::set_value_at_thread_exit(R&& r);
复制代码

当异步结果已经存有一个值或一个异常,那么将抛出std::future_error型异常,伴随错误码为std::future_errc::promise_already_satisfied

set_exception 成员函数

存储一个异常到与*this关联的异步结果中。

void set_exception(std::exception_ptr e);
复制代码

首先,*this具有关联异步结果。然后将e存储到*this相关的异步结果中。在存储异常后,*this相关的异步结果的状态将置为就绪。任何等待异步结果的阻塞线程将解除阻塞。

当异步结果已经存有一个值或一个异常,那么将抛出std::future_error型异常,伴随错误码为std::future_errc::promise_already_satisfied

#include <iostream>
#include <future>

int main() {
    std::promise<int> p;
    std::future<int> f = p.get_future();
 
    try {
        // 可能抛出的代码
        throw std::runtime_error("Example");
    } catch(...) {
        try {
            // 存储任何抛出的异常于 promise
            p.set_exception(std::current_exception());
        } catch(...) {} // set_exception() 亦可能抛出
    }

    // 存储任何抛出的异常于 promise,自定义异常需要使用make_exception_ptr转换一下
    // p.set_exception(std::make_exception_ptr(std::runtime_error("Example")));

    try {
        std::cout << f.get();
    } catch(const std::exception& e) {
        std::cout << "Exception from the thread: " << e.what() << '\n';
    }
}
复制代码

输出:

Exception from the thread: Example
复制代码

set_exception_at_thread_exit 成员函数

存储一个异常到共享状态中,而不立即使状态就绪,直到当前线程退出,销毁所有拥有线程局域存储期的变量后,异步结果才被置为就绪。

void set_exception_at_thread_exit(std::exception_ptr e);
复制代码

*this无共享状态。伴随错误码为std::future_errc::no_state。当异步结果已经存有一个值或一个异常,那么将抛出std::future_error型异常,伴随错误码为std::future_errc::promise_already_satisfied

从未设值

如果promise对象直到析构销毁时,都没有调用set_xx接口设置过任何值,则promise会在析构时自动设置为std::future_error错误异常,这会造成std::future.get立刻解除阻塞并抛出std::future_error异常。

#include <iostream> // std::cout, std::endl
#include <thread>   // std::thread
#include <future>   // std::promise, std::future
#include <chrono>   // seconds
using namespace std::chrono;

void future_get(std::future<int> future) {
    try {
        future.get();
    } catch(std::future_error &e) {
        std::cerr << e.code() << "\n" << e.what() << std::endl;
    }
}

int main() {
    std::thread thread;
    {
        // 如果promise不设置任何值
        // 则在promise析构时会自动设置为future_error
        // 这会造成future.get抛出该异常
        std::promise<int> promise;
        thread = std::thread(future_get, promise.get_future());
    }
    std::cout << "promise destory here" << std::endl;
    thread.join();

    return 0;
}
复制代码

输出:

future:4
std::future_error: Broken promise
promise destory here
复制代码

存储自定义异常

通过std::promise::set_exception函数可以设置自定义异常,该异常最终会被传递到std::future,并在其get函数中被抛出。

自定义异常可以通过位于头文件exception下的std::make_exception_ptr函数转化为std::exception_ptr

#include <iostream>
#include <future>
#include <thread>
#include <exception>  // std::make_exception_ptr


struct MyException : public std::exception {
  const char * what () const throw ()
  {
    return "Test promise exception";
  }
};


void catch_exception(std::future<void> &future) {
    try {
        future.get();
    } catch (MyException &e) {
        std::cout << "MyException: " << e.what() << std::endl;
    }
}

int main() {
    std::promise<void> promise;
    std::future<void> future = promise.get_future();

    std::thread thread(catch_exception, std::ref(future));
    // 自定义异常需要使用make_exception_ptr转换一下
    promise.set_exception(std::make_exception_ptr(MyException()));
    
    thread.join();
    return 0;
}
复制代码

输出:

MyException: Test promise exception
复制代码

std::promise<void>

std::promise<void>是合法的,它是std::promise的特例化。此时std::promise.set_value不接受任何参数,仅用于通知关联的std::future.get()解除阻塞,void 特化,仅用于交流无状态事件。

#include <iostream>       // std::cout
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

void print_void(std::future<void>& fut) {
    fut.get(); // 获取共享状态的值void.
    std::cout << "value: void"  << '\n'; // 打印 value: 10.
}

int main () {
    std::promise<void> prom; // 生成一个 std::promise<void> 对象.
    std::future<void> fut = prom.get_future(); // 和 future 关联.
    std::thread t(print_void, std::ref(fut)); // 将 future 交给另外一个线程t.
    prom.set_value(); // 设置共享状态的值, 此处和线程t保持同步.
    t.join();
    return 0;
}
复制代码

std::promise所在线程退出时

std::promise支持定制线程退出时的行为:

std::promise::set_value_at_thread_exit线程退出时,std::future收到通过该函数设置的值。 std::promise::set_exception_at_thread_exit线程退出时,std::future则抛出该函数指定的异常。

#include <iostream>
#include <future>
#include <thread>
#include <chrono>

std::time_t now() {
    auto t0 = std::chrono::system_clock::now();
    std::time_t time_t_today = std::chrono::system_clock::to_time_t(t0);
    return time_t_today;  // seconds
}

int main() {
    using namespace std::chrono_literals;
    std::promise<int> p;
    std::future<int> f = p.get_future();

    std::thread([&p] {
          std::this_thread::sleep_for(1s);
          p.set_value_at_thread_exit(9);
    }).detach();
 
    std::cout << now() << "->Waiting...\n" << std::flush;
    f.wait();
    std::cout << now() << "->Done!\nResult is: " << f.get() << '\n';
}
复制代码

输出:

1647995534->Waiting...
1647995535->Done!
Result is: 9
复制代码

从上面的输出可以看到,waiting和Done之间是有间隔1s钟的,也就是说set_value_at_thread_exit确实需要等线程结束后才会设值get为就绪解除阻塞。

std::future_errc

下面简单介绍一下std::future_errc类型,future_errc 类枚举 : 是 future_error 类报告的所有错误提供符号名称。future_errc枚举类型的值可用于创建error_condition对象,可以用于与future_error异常返回的值进行比较。

名称 示意
broken_promise 0 与其关联的 std::promise 生命周期提前结束。
future_already_retrieved 1 重复调用 get() 函数。
promise_already_satisfied 2 与其关联的 std::promise 重复 set。
no_state 4 无共享状态。
#include <iostream>     // std::cerr
#include <future>       // std::promise, std::future_error, std::future_errc

int main () {
  std::promise<int> prom;

  try {
    prom.get_future();
    prom.get_future();   // throws std::future_error with future_already_retrieved
  }
  catch (std::future_error& e) {
    if (e.code() == std::make_error_condition(std::future_errc::future_already_retrieved))
    std::cerr << "[future already retrieved]\n";
    else std::cerr << "[unknown exception]\n";
  }

  return 0;
}
复制代码

关于std::promise就是这些了,注意简单介绍std::promise的能力以及如何使用,如果想更深入了解该类,建议直接阅读一下源码。

参考

https//www.apiref.com/cpp-zh/cpp/…

Guess you like

Origin juejin.im/post/7079952174884585509