在此之前的几篇文章分别介绍和分析了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)
};