《探索C++多线程》:future源码(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hujingshuang/article/details/70916610

        在此之前的几篇文章分别介绍和分析了C++11.0标准库中支持多线程的几个头文件:<thread>、<mutex>、<condition_variable>。那么接下来乘热打铁,在这篇文章中将要分析的是:多线程的异步操作。

        多线程的异步操作源码在头文件<future>中。我们先来看一看<future>中都定义了些什么类、函数:

classes future future_error packaged_task  promise shared_future
enum classes future_errc future_status launch

functions async future_categoy


        从上表中可以看出,<future>中提供的外部接口就有6个class,3个enum class,2个function,在这篇文章中,我们先来分析上表中 用颜色标记的几个接口,剩下的将在下一篇博文中进行分析。

std::future

        future提供了用来访问异步操作结果的机制。当一个异步操作我们不可能马上获取操作结果时,就可以用同步等待的方式来获取,通过查询future的状态(future_status)来获取异步操作结果。

        future的状态定义在future_status中,如下:

enum class future_status {
	ready,			// 异步操作已完成,共享状态变为了ready
	timeout,		// 异步操作超时,在规定时间内共享状态没有变为ready
	deferred		// 异步操作还没开始,共享状态包含了一个deferred函数
};

class future中提供了几个方法:get()、wait()、wait_for()、wait_until()。在介绍future之前,先来介绍一个函数:std::async。

std::async

返回future对象,其关联的异步状态管理一个函数对象。可能从概念上我们还把握不到什么,下面看一段代码:

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

using namespace std;

// 这是一个用于检验素数的函数
bool is_prime(int x) {
    cout << "Calculating. Please, wait...\n";
    for (int i = 2; i < x; ++i) {
        if (x % i == 0) {
            return false;
        }
    }
    return true;
}

int main()
{
    // 异步地调用函数is_prime(313222313)
    future<bool> fut = async(is_prime, 313222313);

    cout << "Checking whether 313222313 is prime.\n";

    bool ret = fut.get();      // 等待is_prime返回

    if (ret) {
        cout << "It is prime!\n";
    } else {
        cout << "It is not prime.\n";
    }

    return 0;
}

        我们从代码中看到,async关联了is_prime函数且传入参数为313222313,async返回一个关联了异步状态的future对象。在关联完is_prime函数后,便异步地执行main函数和is_prime函数(实际上就是两个线程了)。而is_prime的返回值,可以通过future对象(代码中的fut)的get()方法获取。这里注意,在main中调用fut.get(),如果is_prime函数已经执行完了,那么可以直接获取到其返回值;如果is_prime函数还没有执行完,那么将阻塞直到得到其返回值。

        实际上,在std::async中的第一个参数可以设置关联函数的异步启动策略,可以设定如下:

future<bool> fut = async(luanch::async, is_prime, 313222313);		// 1
future<bool> fut = async(luanch::defered, is_prime, 313222313);		// 2
future<bool> fut = async(luanch::any, is_prime, 313222313);			// 3
future<bool> fut = async(luanch::sync, is_prime, 313222313);		// 4
其中这四种异步启动策略,定义如下 ,另外在后面会我也会讲解到。

// 异步启动的策略
enum class launch {
	async = 0x1,				// 异步启动,在调用std::async()时创建一个新的线程以异步调用函数,并返回future对象;
	deferred = 0x2,				// 延迟启动,在调用std::async()时不创建线程,直到调用了future对象的get()或wait()方法时,才创建线程;
	any = async | deferred,		// 自动,函数在某一时刻自动选择策略,这取决于系统和库的实现,通常是优化系统中当前并发的可用性
	sync = deferred
};
讲了这么多,我们现在来总结一下std::async:

        1、异步接口std::async可以自动创建线程去调用线程函数,并返回一个std::future对象,能方便的获取线程的执行结果;

        2、提供了函数的异步启动策略,可以延迟启动。

到此为止,我们再回过头来接着讲std::future,实际上,future对象的获取,可以通过以下三种办法得到:

        1、async
        2、promise::get_future
        3、packaged_task::get_future

其中方法1,我们已经在std::async中接触过了,那么后面的方法2、3,将在讲解std::promise和std::package_task时会讲到。

接下来,我们继续分析std::future对象的几种方法:get()、wait()、wait_for()、wait_until()。

std::future::get()

        1、当共享状态就绪时,返回值存放在共享状态中或抛出异常;

        2、当共享状态尚未就绪,则将阻塞线程并等待,直到准备就绪;

        3、一旦共享状态准备就绪,函数就不阻塞了,将会返回(或抛出)并释放共享状态,使future对象不再有效,即成员函数在每个future对象的共享状态下至多调用一次。

std::future::wait()

        1、等待共享状态就绪。若共享状态尚未准备就绪,则将阻塞线程并等待,直到准备就绪;

        2、一旦共享状态准备就绪,函数就不阻塞了,并返回其值(既不读取值,也不抛出异常)。

我们来看一个例子:

#include <iostream>       // std::cout
#include <future>         // std::async, std::future
#include <chrono>         // std::chrono::milliseconds
using namespace std;
bool is_prime (int x) {
  for (int i = 2; i < x; ++i) {
  	if (x % i == 0) {
		  return false;
	  }
  }
  return true;
}

int main () {
  future<bool> fut = async (is_prime, 194232491); 

  cout << "checking...\n";
  fut.wait();			// 等待共享状态就绪
  cout << "\n194232491 ";

  if (fut.get())      	// 由于wait()已保证了共享状态已经就绪,所以get()将不会阻塞
    cout << "is prime.\n";
  else
    cout << "is not prime.\n";

  return 0;
}
std::future::wait_for()

        1、在一定时间内等待共享状态就绪;

        2、如果共享状态尚未就绪,则将阻塞线程并等待,直到就绪或经过设定的时间rel_time;

举个例子:

#include <iostream>       // std::cout
#include <future>         // std::async, std::future
#include <chrono>         // std::chrono::milliseconds

bool is_prime (int x) {
  for (int i = 2; i < x; ++i) {
  	if (x % i == 0) {
		  return false;
	  }
  }
  return true;
}

int main () {
  future<bool> fut = async (is_prime, 700020007); 

  cout << "checking, please wait";
  chrono::milliseconds span (100);

  while (fut.wait_for(span) == future_status::timeout) { // 若超时,则继续wait_for
	cout << '.';
  }

  bool x = fut.get(); // 此时能保证共享状态已经就绪,因此get()不会阻塞

  cout << "\n700020007 " << (x ? "is" : "is not") << " prime.\n";

  return 0;
}
std::future::wait_until()

        1、等待共享状态就绪,直到指定的时间点到来;

        2、如果共享状态尚未就绪,则将阻塞线程并等待,直到就绪或指定的时间点到来。


        好了,以上就是std::future的用法了。另外,我们可以注意到,在上面的代码中用到了future_status::timeout来指示函数返回的原因,future_staus是一个enum class,其定义如下:

enum class future_status {
	ready,				// 共享状态就绪
	timeout,			// 超时
	deferred			// 共享状态包含了延迟(std::async使用了参数std::launch::defered)
};

猜你喜欢

转载自blog.csdn.net/hujingshuang/article/details/70916610