C++ 多线程:std::promise

C++ 多线程09:std::promise

概念

std::promise是一个模板类: template<typename ResultType> class promise。其泛型参数ResultTypestd::promise对象保存的值的类型,例如std::promise<int>ResultType可以是void类型。std::promise类型模板提供设置异步结果的方法,这样其他线程就可以通过std::future实例来读取该结果。std::promisestd::future合作共同实现了多线程间通信。

在构造std::promise对象时,该对象与新的共享状态(shared state)关联。通过调用std::promiseget_future函数,可以将该共享状态与std::future对象关联。调用get_future之后,两个对象共享相同的共享状态:std::promise对象是异步提供程序(asynchronous provider),应在某个时刻为共享状态设置一个值。std::future对象是个异步返回对象,可以检索共享状态的值,并在必要时等待其准备就绪。 需要注意的是:set_value只能被调用一次,多次调用会抛出std::future_error异常。事实上std::promise::set_xxx函数会改变std::promise的状态为ready,再次调用时发现状态已要是reday了,则抛出异常。

std::promise实例是可以MoveConstructible(移动构造)和MoveAssignable(移动赋值),但是不能CopyConstructible(拷贝构造)和CopyAssignable(拷贝赋值)。

类型定义

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);
};
复制代码

默认构造函数

通过访问新的空共享状态来构造一个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中关联异步结果相交换。二者返回来亦然。

以下是一个使用 swap 成员函数将两个 std::promise 实例中的关联异步结果进行交换的例子:

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

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

int main () {
    std::promise<int> prom1;
    std::promise<int> prom2;

    std::thread th1(print_promise, std::ref(prom1));
    std::thread th2(print_promise, std::ref(prom2));

    prom1.set_value(10);
    prom2.set_value(20);

    th1.join();
    th2.join();

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

    prom1.swap(prom2);  // 使用 swap 成员函数交换 prom1 和 prom2。

    std::thread th3(print_promise, std::ref(prom1));
    std::thread th4(print_promise, std::ref(prom2));

    prom1.set_value(20);
    prom2.set_value(10);

    th3.join();
    th4.join();

    return 0;
}

在这个例子中,我们首先创建了两个 std::promise 对象 prom1prom2,并分别在两个线程 th1th2 中打印它们的值。然后,我们使用 swap 成员函数交换了 prom1prom2 中的值。最后,我们再次在两个新的线程 th3th4 中打印 prom1prom2 的值,可以看到它们的值已经交换了。希望这个例子对你有所帮助!


析构函数

放弃(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_promise0与其关联的 std::promise 生命周期提前结束。
future_already_retrieved1重复调用 get() 函数。
promise_already_satisfied2与其关联的 std::promise 重复 set。
no_state4无共享状态。
#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/…

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在C++中,std::promise是一种线程间通信机制,它允许一个线程在另一个线程中设置一个值。在使用std::promise时,一个线程可以将一个值传递给另一个线程,而无需等待该线程返回。具体而言,std::promise允许线程A创建一个std::future对象并将其返回给线程B,线程B可以在某个时间点通过调用std::promise的set_value()方法来设置std::future对象的值。一旦std::future对象被设置了值,线程A可以通过调用std::future对象的get()方法来获取该值。在使用std::promise时,需要注意的是,一个std::promise对象只能被设置一次值,因此在使用它时需要小心。 ### 回答2: std::promiseC++标准库中的一个类,用于在线程之间传递异步操作的结果。它通常与 std::future 一起使用,通过 std::promise 传递数据给 std::future 对象,在另一个线程中可以获取到该数据。 std::promise 的用法可以分为三个步骤:创建 promise 对象、获取 future 对象、设置 promise 对象的值。 首先,我们需要创建一个 promise 对象,这可以通过构造函数来完成,例如:std::promise<int> myPromise; 这样就创建了一个用于传递 int 类型数据的 promise 对象。 接下来,我们需要使用 promise 对象获取对应的 future 对象,通过调用 promise 对象的成员函数 std::promise::get_future() 来获取,例如:std::future<int> myFuture = myPromise.get_future(); 这样就获得了一个用于接收 int 类型数据的 future 对象。 最后,我们可以通过设置 promise 对象的值来传递数据给 future 对象。这可以通过调用 promise 对象的成员函数 std::promise::set_value() 来完成,例如:myPromise.set_value(42); 这样就将值 42 传递给了之前创建的 future 对象。 在另一个线程中,我们可以通过调用 future 对象的成员函数 std::future::get() 来获取到设置的值,例如:int result = myFuture.get(); 这样就可以获取到之前设置的值 42。 需要注意的是,一旦 promise 对象被设置了值,就不能再次设置。如果我们不希望在某些情况下传递数据到 future 对象,可以使用 std::promise::set_exception() 来设置一个异常。 总而言之,std::promise 提供了一种方便的机制来在线程之间传递异步操作的结果,通过设置 promise 对象的值,可以在另一个线程中获取到这些值,并进行相应的处理。 ### 回答3: std::promiseC++标准库中的一个类模板,用于在多线程编程中用来存放一个值或者一个异常的容器。它通常与std::future一起使用,将一个值或异常从一个线程传递到另一个线程。 std::promise提供了两个主要的成员函数set_value()和set_exception()来设置值和异常。当一个线程调用set_value()或set_exception()时,std::promise的状态会变为“就绪”状态。其他线程可以通过std::future对象获取std::promise中的值或异常。 使用std::promise的基本步骤如下: 1. 创建一个std::promise对象,例如std::promise<int> p; 2. 创建一个std::future对象,通过std::promise的get_future()成员函数获取,例如std::future<int> f = p.get_future(); 3. 启动一个线程,该线程可以在某个时间点调用p.set_value()或p.set_exception()来设置值或异常。 4. 在需要的地方,通过std::future对象的get()成员函数获取std::promise中的值或异常。 例如,通过std::promise实现一个计算线程和一个读取结果的线程的示例: ``` #include <iostream> #include <thread> #include <functional> #include <future> void calculate(std::promise<int>& resultPromise, int a, int b) { int result = a + b; resultPromise.set_value(result); } int main() { std::promise<int> resultPromise; std::future<int> resultFuture = resultPromise.get_future(); std::thread calculationThread(calculate, std::ref(resultPromise), 3, 4); // 等待计算线程完成 calculationThread.join(); // 读取结果 int result = resultFuture.get(); std::cout << "Result: " << result << std::endl; return 0; } ``` 这个示例中,计算线程通过调用resultPromise.set_value()来设置结果值,读取结果的线程通过resultFuture.get()来阻塞等待结果值的返回,这样就实现了两个线程之间的值的传递和同步。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁金金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值