深入了解C++11中promise和future的使用

目录
  • Promise和Future
    • 原理
    • Promise和Future模型
    • promise相关函数
    • 多线程std::shared_future
  • promise和future进阶

Promise和Future

原理

C++11中promise和future机制是用于并发编程的一种解决方案,用于在不同线程完成数据传递(异步操作)。

传统方式通过回调函数处理异步返回的结果,导致代码逻辑分散且难以维护。

Promise和Future是一种提供访问异步操作结果的机制,可以在线程之间传递数据和异常消息。

应用场景:顾客在一家奶茶店点了单,服务员给顾客一个单号,当奶茶做好后,服务员更新排号的状态,顾客可以去做自己的事情了,顾客可以通过查询排号来得知奶茶是否做好,当查到奶茶做好了就可以回来取奶茶了。

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

using namespace std;
using namespace std::chrono;

void WaitForMilkTea(future<int>& future)
{
	/*其中获取future结果有三种方式
	 1、auto value = future.get()    get()方法会阻塞等待异步操作结束并返回结果
	 2、std::future_status  方式判断状态 有deferred、timeout、ready三种状态
	 3、可以
	*/

//future_status方法
#if 0
	std::future_status status;
	do {
		status = future.wait_for(std::chrono::milliseconds(500));
		if (status == std::future_status::deferred) {
			std::cout << "deferred!!!" << std::endl; //异步操作还没开始
		} else if (status == std::future_status::timeout) {
			std::cout << "timeout!!!" << std::endl; //异步操作超时
		} else if (status == std::future_status::ready) {
			std::cout << "ready!!!" << std::endl; //异步操作已经完成
		}
	} while (status != std::future_status::ready);

	//通过判断future_status状态为ready时才通过get()获取值
	auto notice = future.get();
	std::cout << "WaitForMilkTea receive value:" << notice << std::endl;
#endif

//get()方法
#if 0
	auto notice = future.get();   //get阻塞等待直到异步处理结束返回值
	std::cout << "WaitForMilkTea receive value:" << notice << std::endl;
#endif

//wait()方法
	future.wait(); //和get()区别是wait只等待异步操作完成,没有返回值
	auto notice = future.get();
	std::cout << "WaitForMilkTea receive value:" << notice << std::endl;
}

void MakeTea(promise<int>& promise)
{
	//do something 这里先睡眠5s
	std::this_thread::sleep_for(std::chrono::seconds(5));
	promise.set_value(1);
	std::this_thread::sleep_for(std::chrono::seconds(10));
	std::cout << "MakeTea Thread quit!!!" << std::endl;
}

int main()
{
	promise<int> pNotice;
	//获取与promise相关联的future
	future<int> pFuture = pNotice.get_future();

	thread Customer(WaitForMilkTea, ref(pFuture));
	thread Worker(MakeTea, ref(pNotice));

	Customer.join();
	Worker.join();
}

其中future_status枚举如下:

名称 示意
ready 0 就绪
timeout 1 等待超时
deferred 2 延迟执行(与std::async配合使用)

future_errc 枚举 : 为 future_error 类报告的所有错误提供符号名称。

名称 示意
broken_promise 0 与其关联的 std::promise 生命周期提前结束
future_already_retrieved 1 重复调用 get() 函数
promise_already_satisfied 2 与其关联的 std::promise 重复 set
no_state 4 无共享状态

Promise和Future模型

流程如下:

1.线程1初始化一个promise和future对象,将promise对象传递给线程2,相当于线程2对线程1的一个承诺

2.future相当于一个承诺,用于获取未来线程2的值

3.线程2接受一个promise,需要将处理结果通过promise返回给线程1

4.线程1想要获取数据,此时线程2还未返回promise就阻塞等待处,直到线程2的数据可达

promise相关函数

std::future负责访问, std::future是一个模板类,它提供了可供访问异步执行结果的一种方式。

std::promise负责存储, std::promise也是一个模板类,它提供了存储异步执行结果的值和异常的一种方式。

总结:std::future负责访问,std::promise负责存储,同时promise是future的管理者

std::future

名称 作用
operator= 移动 future 对象,移动!
share() 返回一个可在多个线程中共享的 std::shared_future 对象
get() 获取值(类型由模板类型决定)
valid() 检查 future 是否处于被使用状态,也就是它被首次在首次调用 get() 或 share() 前。建议使用前加上valid()判断
wait() 阻塞等待调用它的线程到共享值成功返回
wait_for() 在规定时间内 阻塞等待调用它的线程到共享值成功返回
wait_until() 在指定时间节点内 阻塞等待调用它的线程到共享值成功返回

1、普通构造函数, 默认无参构造函数

2、带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态。

3、拷贝构造函数和普通=赋值运算符默认禁止

4、移动构造函数

5、移动赋值运算符

  • std::future仅在创建它的std::promise(或者std::async、std::packaged_task)有效时才有用,所以可以在使用前用valid()判断
  • std::future可供异步操作创建者用各种方式查询、等待、提取需要共享的值,也可以阻塞当前线程等待到异步线程提供值。
  • std::future一个实例只能与一个异步线程相关联,多个线程则需要使用std::shared_future。

std::promise

成员函数:

名称 作用
operator= 从另一个 std::promise 移动到当前对象。
swap() 交换移动两个 std::promise。
get_future() 获取与其管理的std::future
set_value() 设置共享状态值,此后promise共享状态标识变为ready
set_value_at_thread_exit() 设置共享状态的值,但是不将共享状态的标志设置为 ready,当线程退出时该 promise 对象会自动设置为 ready
set_exception() 设置异常,此后promise的共享状态标识变为ready
set_exception_at_thread_exit() 设置异常,但是到该线程结束时才会发出通知

1、std::promise::get_future:返回一个与promise共享状态相关联的future对象

2、std::promise::set_value:设置共享状态的值,此后promise共享状态标识变为ready

3、std::promise::set_exception:为promise设置异常,此后promise的共享状态标识变为ready

4、std::promise::set_value_at_thread_exit:设置共享状态的值,但是不将共享状态的标志设置为 ready,当线程退出时该 promise 对象会自动设置为 ready(注意:该线程已设置promise的值,如果在线程结束之后有其他修改共享状态值的操作,会抛出future_error(promise_already_satisfied)异常)

5、std::promise::swap:交换 promise 的共享状态

  • std::promise的set相关函数和get_future()只能被调用一次,多次调用会抛出异常
  • std::promise作为使用者的异步线程中应当注意到共享变量的生命周期、是否被set问题。如果没被set而线程就结束了,future端就会抛出异常。

多线程std::shared_future

std::future 有个非常明显的问题,就是只能和一个 std::promise 成对绑定使用,也就意味着仅限于两个线程之间使用。

那么多个线程是否可以呢,可以!就是 std::shared_future。

std::shared_future 也是一个模板类,它的功能定位、函数接口和 std::future 一致,不同的是它允许给多个线程去使用,让多个线程去同步、共享:

它的语法是:

【语法】【伪代码】std::shared_future<Type> s_fu(pt.get_future());

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

using namespace std;
using namespace std::chrono;

void futureHandleEntry(std::shared_future<int>& future)
{
	if (future.valid()) {
		future.wait();
		std::cout << "thread:[" << std::this_thread::get_id() << "] value=" << future.get() << std::endl;
		std::cout << "thread:[" << std::this_thread::get_id() << "] quit!!!" << std::endl;
	}
	else {
		std::cout << "future is invalid!!!" << std::endl;
	}
}

int main()
{
    std::promise<int> promise;
	//获取shared_future用于多线程访问异步操作结果
	std::shared_future<int> future = promise.get_future();

	std::thread t1(&futureHandleEntry, ref(future));
	std::thread t2(&futureHandleEntry, ref(future));
	std::thread t3(&futureHandleEntry, ref(future));

	std::cout << "main thread running!!!" << std::endl;
	//主线程sleep5s后去set_value
	std::this_thread::sleep_for(std::chrono::seconds(5));
	promise.set_value(10);

	t1.join();
	t2.join();
	t3.join();
}

promise和future进阶

我们知道异常的场景:

1、当重复调用promise的set_value会导致抛出异常

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

using namespace std;

void threadEntry(std::future<int>& future)
{
	try {
		auto value = future.get();
		std::cout << "value=" << value << std::endl;
	}
	catch (std::future_error& error) {
		std::cerr << error.code() << "\n" << error.what() << std::endl;
	}
}
int main()
{
	std::promise<int> promise;
	std::future<int> future = promise.get_future();

	std::thread t1(threadEntry, ref(future));
	/*主线程promise多次调用set_value会抛出future_error异常
	*/
	std::this_thread::sleep_for(std::chrono::seconds(2));
	promise.set_value(1);
	promise.set_value(1);

	t1.join();
}

在linux中运行结果如下: 会有Promise already satisfied的错误提示

2、 当std::promise不设置值时线程就退出

如果promise直到销毁时,都未设置过任何值,则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 read(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(read, promise.get_future());
   }
    thread.join();

    return 0;
}

3、通过std::promise让std::future抛出异常

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

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

void catch_error(std::future<void> &future) {
    try {
       future.get();
    } catch (std::logic_error &e) {
       std::cerr << "logic_error: " << e.what() << std::endl;
   }
}

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

    std::thread thread(catch_error, std::ref(future));
    // 自定义异常需要使用make_exception_ptr转换一下
    promise.set_exception(
       std::make_exception_ptr(std::logic_error("caught")));

      thread.join();
     return 0;
}

std::promise虽然支持自定义异常,但它并不直接接受异常对象:

// std::promise::set_exception函数原型
2void set_exception(std::exception_ptr p);

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

std::promise

通过上面的例子,我们看到std::promise<void>

是合法的。此时std::promise.set_value不接受任何参数,仅用于通知关联的std::future.get()解除阻塞。

std::promise所在线程退出时

std::async(异步运行)时,开发人员有时会对std::promise所在线程退出时间比较关注。std::promise支持定制线程退出时的行为:

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

到此这篇关于深入了解C++11中promise和future的使用的文章就介绍到这了,更多相关C++ promise future内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++11的future和promise、parkged_task使用

    future 的介绍 A future is an object that can retrieve a value from some provider object or function, properly synchronizing this access if in different threads. 它可以从异步的对象或者函数任务中获取结果,它通常和std::async.promise.packaged_task相互调用. future对象通常是在valid有效的情况下可以使用,默

  • C++11 <future>中std::promise 介绍

    前面两讲<C++11 并发指南二(std::thread 详解)>,<C++11 并发指南三(std::mutex 详解) >分别介绍了 std::thread 和 std::mutex,相信读者对 C++11 中的多线程编程有了一个最基本的认识,本文将介绍 C++11 标准中 <future> 头文件里面的类和相关函数. <future> 头文件中包含了以下几个类和函数: Providers 类:std::promise, std::package_tas

  • 深入了解C++11中promise和future的使用

    目录 Promise和Future 原理 Promise和Future模型 promise相关函数 多线程std::shared_future promise和future进阶 Promise和Future 原理 C++11中promise和future机制是用于并发编程的一种解决方案,用于在不同线程完成数据传递(异步操作). 传统方式通过回调函数处理异步返回的结果,导致代码逻辑分散且难以维护. Promise和Future是一种提供访问异步操作结果的机制,可以在线程之间传递数据和异常消息. 应

  • C++11中std::future的具体使用方法

    C++11中的std::future是一个模板类.std::future提供了一种用于访问异步操作结果的机制.std::future所引用的共享状态不能与任何其它异步返回的对象共享(与std::shared_future相反)( std::future references shared state that is not shared with any other asynchronous return objects (as opposed to std::shared_future)).一

  • 理解JavaScript中Promise的使用

    Javascript 采用回调函数(callback)来处理异步编程.从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验.于是便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题.本文先介绍 Promises 相关规范,然后再通过解读一个迷你的 Promises 以加深理解. 什么是 Promise 一个 Promise 对象代表一个目前还不可用,但是在未来的

  • vue中promise的使用及异步请求数据的方法

    下面给大家介绍vue中promise的使用 promise是处理异步的利器,在之前的文章<ES6之promise>中,我详细介绍了promise的使用, 在文章<js动画实现&&回调地狱&&promise>中也提到了promise的then的链式调用, 这篇文章主要是介绍在实际项目中关于异步我遇到的一些问题以及解决方法,由此来加深对promise的进一步理解. 背景 进入商品页,商品页的左侧是分类,右侧是具体的商品,一旦进入商品页,就把所有分类的商品

  • C++11中std::packaged_task的使用详解

    C++11中的std::packaged_task是个模板类.std::packaged_task包装任何可调用目标(函数.lambda表达式.bind表达式.函数对象)以便它可以被异步调用.它的返回值或抛出的异常被存储于能通过std::future对象访问的共享状态中. std::packaged_task类似于std::function,但是会自动将其结果传递给std::future对象. std::packaged_task对象内部包含两个元素:(1).存储的任务(stored task)

  • C++11中std::async的使用详解

    C++11中的std::async是个模板函数.std::async异步调用函数,在某个时候以Args作为参数(可变长参数)调用Fn,无需等待Fn执行完成就可返回,返回结果是个std::future对象.Fn返回的值可通过std::future对象的get成员函数获取.一旦完成Fn的执行,共享状态将包含Fn返回的值并ready. std::async有两个版本: 1.无需显示指定启动策略,自动选择,因此启动策略是不确定的,可能是std::launch::async,也可能是std::launch

  • C++11中多线程编程-std::async的深入讲解

    前言 C++11中提供了异步线程接口std::async,std::async是异步编程的高级封装,相对于直接使用std::thread,std::async的优势在于: 1.std::async会自动创建线程去调用线程函数,相对于低层次的std::thread,使用起来非常方便: 2.std::async返回std::future对象,通过返回的std::future对象我们可以非常方便的获取到线程函数的返回结果: 3.std::async提供了线程的创建策略,可以指定同步或者异步的方式去创建

  • 正则表达式简介及在C++11中的简单使用教程

    正则表达式Regex(regular expression)是一种强大的描述字符序列的工具.在许多语言中都存在着正则表达式,C++11中也将正则表达式纳入了新标准的一部分,不仅如此,它还支持了6种不同的正则表达式的语法,分别是:ECMASCRIPT.basic.extended.awk.grep和egrep.其中ECMASCRIPT是默认的语法,具体使用哪种语法我们可以在构造正则表达式的时候指定. 正则表达式是一种文本模式.正则表达式是强大.便捷.高效的文本处理工具.正则表达式本身,加上如同一门

  • 轻松理解iOS 11中webview的视口

    iOS 11在状态栏区域带来了一些新的,也许是不直观的行为,这对使用Apache Cordova或Ionic等工具的开发人员尤为重要.尤其是这种行为变化会影响到任何基于Web的应用程序,这些应用程序在进行iOS 11构建时使用fixed定位标题栏.此文章可帮助您了解iOS 11中的Webview视口. 注意:现有应用程序将继续工作,因为它们始终可以对其视口行为进行更改.这只会影响使用Xcode 9和iOS 11的目标编译的应用程序. 要了解这些变化,我们需要看看它的历史. 状态栏与安全区 在早起

  • PHP 5.6.11中CURL模块问题的解决方法

    按照网上的教程写了一个cURL的小例子,在apache环境下执行,一点反应也没有,放在IIS环境里就ok的,感觉问题一定出在动态连接库上,因为配置文件里的php_curl.dll已经打开了,而且在iis上ok: 网上找了一些解决方案: 设置了[环境变量]:phpext,PHPRC:无效 把php_curl.dll 放到apache/bin下:无效 检查了一下apache/bin目录下也有这两个libeay32.dll,ssleay32.dll个文件:没问题 最后试了一下,把当前php根目录下的l

随机推荐