【C++11 多线程】future与promise(八)

一、C++11为什么要引入std::future?

我们经常会遇到需要从线程中返回异步任务结果的情况。例如在程序中,我们创建了一个压缩给定文件夹的线程,并且我们希望该线程能够返回新的 zip 文件的名称和大小。

在 C++11 之前的老方法是使用指针在线程间共享数据:

  • 传递一个指针到新的线程中,该线程将在其中设置数据。直到主线程继续等待使用条件变量。当新线程设置数据并通知条件变量时,主线程将唤醒并从该指针处获取数据。
  • 为了实现这一简单功能,我们使用了一个条件变量、一个 mutex 锁和一个指针,来实现捕获返回值。
#include<iostream>
#include<thread>
#include<mutex>

void fun(int x, int y, int* ans) {
	*ans = x + y;
}

int main()
{
	int a = 10;
	int b = 8;

	int* sum = new int(0);
	std::thread t(fun, a, b, sum);
	t.join();

	// 获取线程的"返回值"
	std::cout << *sum << std::endl; // 输出:18
	delete sum;
    
	system("pause");
	return 0;
}

可以看到还需要使用外部变量,比较麻烦。如果我们想要该线程在不同的时间点返回 3 个不同的值,问题会变得更加复杂,有没有一种简单的方法来从线程处获取返回值呢?答案是 使用std::future

二、std::future介绍

C++11 提供了std::future类模板,future 对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果。

在 C++ 标准库中,有两种“期望”,使用两种类型模板实现(这里主要介绍的是唯一期望):

  • 唯一期望(unique futuresstd::future<>) 实例只能与一个指定事件相关联。
  • 共享期望(shared futures, std::shared_future<>) 实例能关联多个事件。

事实上,一个std::future对象在内部存储一个将来会被某个 provider 赋值的值,并提供了一个访问该值的机制,通过get()成员函数实现。但如果有人试图在get()函数可用之前通过它来访问相关的值,那么get()函数将会阻塞,直到该值可用。

一个有效的std::future对象通常由以下三种 Provider 创建,并和某个共享状态相关联。Provider 可以是函数或者类,他们分别是:

  • std::async函数,本文后面会介绍
  • std::promise::get_future,get_future 为 promise 类的成员函数
  • std::packaged_task::get_future,此时 get_future为 packaged_task 的成员函数

C++11 提供的 <future> 头文件中包含了以下几个类和函数:

  • Providers 类:std::promise, std::package_task
  • Futures 类:std::future, shared_future.
  • Providers 函数:std::async()
  • 其他类型:std::future_error, std::future_errc, std::future_status, std::launch

三、std::future构造函数

std::future一般由上面三种 Provider 创建,不过也提供了构造函数:

// default
future() noexcept;
// copy [deleted] 
future (const future&) = delete;
// move
future (future&& x) noexcept;

不过std::future的拷贝构造函数是被禁用的,只提供了默认的构造函数和 move 构造函数(注:C++ 新特性)。另外,std::future的普通赋值操作也被禁用,只提供了 move 赋值操作。如下代码所示:

std::future<int> fut;           // 默认构造函数
fut = std::async(do_some_task);   // move-赋值操作。

四、std::future成员函数

其成员函数如下:

  • std::future::valid()

    检查 future 对象是否拥有共享状态,参照构造函数只有两种可用,由默认构造函数创建的 future 对象显然不具有共享状态,即valid()=false,除非它被 move 赋值过;而移动构造函数创建的 future 对象往往拥有共享状态,只不过是否可以立即调用 get() 访问还需要确认共享状态标志是否已被设置为 ready。

  • std::future::get()

    阻塞式获得共享状态的值,如果 future 对象调用 get() 时,共享状态标志尚未被设置为 ready,那么本线程将阻塞至其变为 ready。

  • std::future::wait()

    等待共享状态标志变为 ready,在此之前线程将会一直阻塞。

  • std::future::wait_for()

    与 wait() 不同,wait_for() 只会允许为此等待一段时间 _Rel_time,耗尽这个时间共享状态标志仍不为 ready,wait_for() 一样会返回。

  • std::future::wait_until()

    与 wait_for() 类似的逻辑,只不过 wait_until() 参考的是绝对时间点。到达时间点 _Abs_time 的时候,wait_until() 就会返回,如果没等到 ready 的话,wait_until 一样会返回。

  • std::future::share()

    返回一个 std::shred_future 对象,调用该函数之后,future 对象不和任何共享状态关联,也就不再是 valid 的了。

其中std::future::wait_for()std::future::wait_until()的返回值如下:

  • future_status::ready:共享状态的标志已经变为 ready,即 Provider 在共享状态上设置了值或者异常。
  • future_status::timeout:超时,即在规定的时间内共享状态的标志没有变为 ready。
  • future_status::deferred:共享状态包含一个 deferred 函数。

上面只是对各函数的简单介绍,具体示例请参考:C++11 并发指南四( 详解三 std::future & std::shared_future)

五、std::promise介绍

std::promise的作用就是提供一个不同线程之间的数据同步机制,它可以存储一个某种类型的值,并将其传递给对应的 future, 即使这个 future 与 promise 不在同一个线程中也可以安全的访问到这个值。

可以通过get_future()来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态(shared state)。set_value()可以设置共享状态的值,此后 promise 的共享状态标志变为 ready。

  • promise 对象是异步 Provider,它可以在某一时刻设置共享状态的值。
  • future 对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为 ready,然后才能获取共享状态的值。

更多内容请参考:C++11 中std::promise 介绍

六、future与promise配合使用示例

下面看一个 future 与 promise 配合使用的示例:

#include<iostream>
#include<thread>
#include<mutex>
#include<atomic>
#include<future>  //std::future std::promise

void fun(int x, int y, std::promise<int>& promiseObj) {
	promiseObj.set_value(x + y);
}

int main()
{
	int a = 10;
	int b = 8;

	// 声明一个promise类
	std::promise<int> promiseObj;
	// 将future和promise关联
	std::future<int> futureObj = promiseObj.get_future();
	// 模板传参的时候使用ref,否则传参失败
	std::thread t(fun, a, b, std::ref(promiseObj));
	t.join();
    
	// 获取线程的"返回值"
	int sum = futureObj.get();
	std::cout << "sum=" << sum << std::endl; // 输出:18

	std::system("pause");
	return 0;
}

参考:

c++11多线程编程(八):std::future , std::promise和线程的返回值

C++多线程获取返回值方法详解


posted @ 2021-03-25 14:17  fengMisaka  阅读(4160)  评论(0编辑  收藏  举报