C++98
标准中并没有线程库的存在。C++11
中才提供了多线程的标准库,提供了thread
、mutex
、condition_variable
、atomic
、future
等相关对象及功能功能。
概述
-
#include <future>
该头文件是线程支持库的一部分。 -
该头文件包提供了线程之间异步的相关对象及函数,如下图所示:
-
std::future是一个模板类。future对象提供访问异步操作结果的机制。
-
#include <future>
中包含两个模板类std::future
、std::shared_future
。
1. std::future
std::future
对象在内部存储一个将来会被赋值的值,并提供了一个访问该值的机制。其类模板声明为:
template <class T> future;
template <class R&> future<R&>; // specialization : T is a reference type (R&)
template <> future<void>; // specialization : T is void
- 有效的 future 是与共享状态(shared state)关联的 future 对象。调用
std::async
、std::packaged_task
、std::promise
能构造并初始化一个std::future
对象。 std::future
成员函数如下:
1.1 构造函数、vaild()
std::future
构造函数声明如下所示:
future() noexcept; // 1. 构造有个空的future对象,此对象共享状态为 false
future(const future&) = delete; // 2. copy [deleted]
future(future&& x) noexcept; // 3. move,(共享状态为有效的future对象)
valid()
: 返回 future 对象是否拥有共享状态
1. 对于使用默认构造产生的 future 对象,该函数返回false
。
2. 通过调用std::async
、std::packaged_task
、std::promise
来初始化 future 对象。该函数返回 true。
3. 一旦调用了std::future::get()
函数,再调用此函数将返回 false。
测试代码:
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <utility> // std::move
int get_value() {
return 10;
}
int main (){
std::future<int> foo,bar;
foo = std::async(get_value);
std::cout << "foo.valid:" << foo.valid() << std::endl; // 1. foo 通过std::async 创建, true
std::cout << "bar.valid:" << bar.valid() << std::endl; // 2. bar 通过默认构造创建, false
bar = std::move(foo);
std::cout << std::endl;
std::cout << "foo.valid:" << foo.valid() << std::endl; // 1. foo对象转移给bar,foo为空的future对象
std::cout << "bar.valid:" << bar.valid() << std::endl; // 2. bar.valid() 为true
std::cout << "bar's value: " << bar.get() << std::endl;
std::cout << "bar.valid:" << bar.valid() << std::endl; // 3. 调用get后,为false
return 0;
}
foo.valid:1
bar.valid:0
foo.valid:0
bar.valid:1
bar’s value: 10
bar.valid:0
1.2 get()、operator=
get()
函数声明
T get(); // 1.
R& future<R&>::get(); // 2. T is references
void future<void>::get(); // 3. T is void
-
get()
函数解释
1. 当共享状态就绪时,返回存储在共享状态中的值。
2. 如果共享状态尚未就绪(即提供者尚未设置其值或异常),则该函数将阻塞调用的线程直到就绪。
3. 一旦共享状态就绪,函数就会取消阻塞并返回(或抛出)释放其共享状态。这使得future对象不再有效:对于每个future共享状态,get()
最多只能调用一次。
4. void版本 不返回任何值,但仍然等待共享状态就绪并释放它。 -
get()
返回值
1. 默认版本返回值为std::move(x),x为共享状态的值。
2. references版本,返回对存储在共享状态中的值的引用。
3. void版本 无返回值。
测试代码
#include<iostream>
#include<thread>
#include<chrono>
#include<future>
double func_sum(const double a, const double b){
double c = a + b;
std::this_thread::sleep_for(std::chrono::seconds(3)); // 延迟3秒返回
return c;
}
int main() {
double num_a = 2.3;
double num_b = 6.7;
future<double> fu = async(func_sum, num_a, num_b); //创建异步线程线程,并将线程的执行结果用fu占位;
std::cout << "正在进行计算,请您耐心等待." << std::endl;
// get()阻塞当前,直至异步线程 return
// future对象的get()方法只能调用一次。
std::cout << "计算结果:" << fu.get() << std::endl;
return 0;
}
正在进行计算,请您耐心等待.
计算结果:9
operator=()
函数声明
future& operator=(future&& rhs) noexcept; // 1. move 的赋值可用
future& operator=(const future&) = delete; // 2. copy 不可用
#include <iostream> // std::cout
#include <future> // std::async, std::future
int get_value() { return 10; }
int main() {
std::future<int> fut; // future 默认构造
fut = std::async (get_value); // 移动赋值
std::cout << "value: " << fut.get() << '\n';
return 0;
}
value: 10
1.3 wait_*()
- <1>.
wait()
: 等待共享状态就绪。如果共享状态尚未就绪(即,提供程序尚未设置其值或异常),则函数将阻塞调用线程,并等待其就绪,一旦共享状态就绪,函数就取消阻塞并返回。
void wait() const;
测试代码:
#include<iostream>
#include<thread>
#include<chrono>
#include<future>
bool is_prime (int x) {
for (int i=2; i<x; ++i) {
if (x%i == 0)
return false;
}
std::this_thread::sleep_for(std::chrono::seconds(3)); // 延迟3秒
return true;
}
int main() {
std::future<bool> fut = std::async(is_prime, 194232491);
std::cout << "checking...\n";
fut.wait(); // wait() 保证在等待返回后准备就绪
std::cout << "\n194232491 ";
if (fut.get()) // get() 不阻塞,直接返回共享值
std::cout << "is prime.\n";
else
std::cout << "is not prime.\n";
return 0;
}
checking…
194232491 is prime.
//194232491
与is prime.
同时打印出。
- <2>.
wait_for()
函数: 在时间跨度rel_time
内等待共享状态就绪, 如果共享状态尚未就绪,则该函数将阻塞调用的线程直到就绪或已达到设置的时间。 - 函数返回值为
future_status
类型:
1.future_status::ready
共享状态已就绪。
2.future_status::timeout
指定时间内未就绪。
3.future_status::deferred
共享状态包含了一个延迟函数。
template <class Rep, class Period>
future_status wait_for(const chrono::duration<Rep,Period>& rel_time) const;
- <3>.
wait_until()
函数: 在目标时间点abs_time
内等待共享状态就绪, 如果共享状态尚未就绪,则该函数将阻塞调用的线程直到就绪或已达到设置的时间。 - 函数返回值为
future_status
类型与上述相同。
template <class Clock, class Duration>
future_status wait_until(const chrono::time_point<Clock,Duration>& abs_time) const;
- 函数
wait_until()
与wait_for()
功能相似,此处以wait_for()
为例。
测试代码:
#include<iostream>
#include<thread>
#include<chrono>
#include<future>
bool is_prime (int x) {
for (int i=2; i<x; ++i) {
if (x%i==0)
return false;
}
return true;
}
int main () {
std::future<bool> fut = std::async (is_prime,700020007);
std::cout << "checking, please wait";
std::chrono::milliseconds span(100);
while (fut.wait_for(span)==std::future_status::timeout) // 等待 100 毫秒,未就绪
std::cout << '.'; // 便打印一个 .
bool x = fut.get();
std::cout << "\n700020007 " << (x?"is":"is not") << " prime.\n";
return 0;
}
checking, please wait…
700020007 is prime.
2. std::Promise
Promise
是一个模板类,可以存储一个T类型的值,以便future
对象(可能在另一个线程中)检索,从而提供一个同步点。- 在构造函数中,
Promise
对象与future
对象相关联,在该状态下,他们可以存储类型为 T 的值。 - 可通过调用
get_future()
将Promise
对象与future
对象相关联,关联后两个对象共享相同的shared state
:
1.Promise
对象是异步提供对象,将会在某一时刻为共享状态设置值。
2.future
对象是一个异步返回对象,可检查共享状态是否就绪,并可以获取共享状态的值。 - 此外,共享状态的生命周期可保持至 与之关联的最后一个对象销毁为止。
template <class T> promise;
template <class R&> promise<R&>; // specialization : T is a reference type (R&)
template <> promise<void>; // specialization : T is void
std::Promise
成员函数如下:
2.1 构造函数、get_future()
- 构造函数如下:
promise(); // 1. default, 共享状态为空
template <class Alloc> promise(allocator_arg_t aa,
const Alloc& alloc); // 2. with allocator
promise(const promise&) = delete; // 3. copy-delete
promise(promise&& x) noexcept; // 4. move,转移x对象的共享状态
get_future()
函数返回与promise
对象共享状态关联的future
对象,该future
对象只能访问绑定的共享状态值和状态,其声明如下:
future<T> get_future();
测试代码:
void print_int(std::future<int>& fut) {
std::cout << "已进入线程th." << std::endl;
int x = fut.get(); // 3. 获取数据,阻塞
std::cout << "value: " << x << '\n';
}
int main () {
std::promise<int> foo;
std::promise<int> bar = std::promise<int>(std::allocator_arg,std::allocator<int>());
std::future<int> fut = bar.get_future(); // 1. future、promise对象相关联
std::thread th(print_int, std::ref(fut)); // 2. fut 作为线程th 的参数
std::this_thread::sleep_for(std::chrono::seconds(3));
bar.set_value(20);
th.join();
return 0;
}
已进入线程th.
value: 20
- 阻塞3秒后,打印
val
值。
2.2 operator=、
- <1>.
operator=
:转移共享状态,当前对象之前的转移共享将被摧毁。 - 若参数对象为左值或没有共享状态,任何影响该参数共享状态的操作都将报错。
promise& operator=(promise&& rhs) noexcept; // 1. 右值赋值ok
promise& operator=(const promise&) = delete;
- <2>.
set_value()
: 将val
存储为共享状态中的值,该状态变为就绪状态。 - 如果有与之关联的future对象正在等待对
future::get
的调用,该函数将取消阻塞并返回val
。 void
版本专门化的成员只是使共享状态就绪,而不设置任何值。
void set_value(const T& val); // 1.
void set_value(T&& val);
void promise<R&>::set_value(R& val); // 2. specializations
void promise<void>::set_value(void);
- <3>.
set_exception()
: 将异常指针p存储在共享状态,该状态变为就绪状态。 - 如果有与之关联的future对象正在等待对
future::get
的调用,该函数将取消阻塞并抛出p所指的异常对象。
测试代码:
#include <iostream> // std::cin, std::cout, std::ios
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future
#include <exception>
void get_int(std::promise<int>& prom) { // 1.参数为 promise
int x;
std::cout << "Please, enter an integer value: ";
std::cin.exceptions(std::ios::failbit); // 设置failbit
try {
std::cin >> x;
prom.set_value(x);
}
catch (std::exception&) {
prom.set_exception(std::current_exception());
}
}
void print_int(std::future<int>& fut) { // 2.参数为 future
try {
int x = fut.get();
std::cout << "value: " << x << '\n';
}catch (std::exception& e) {
std::cout << "[exception caught: " << e.what() << "]\n";
}
}
int main () {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread th1(print_int, std::ref(fut)); // 3. 该线程获取 共享状态值
std::thread th2(get_int, std::ref(prom)); // 4. 该线程设置 共享状态值
th1.join();
th2.join();
return 0;
}
Please, enter an integer value: 10
value: 10
3. std::packaged_task
std::packaged_task
类模板和std::promise
十分相似。其名称直翻为打包任务
,其功能为:打包的任务包装一个可调用元素,并允许异步检索其结果。
template <class T> packaged_task; // undefined
template <class Ret, class... Args> class packaged_task<Ret(Args...)>;
std::packaged_task
与std::function
很类似,但是会自动将结果传输给std::future
对象。std::packaged_task
对象内部包含两个元素:
1. 存储的任务,它是一些可调用的对象(如函数指针、指向成员的指针或函数对象),并返回Ret类型的值。
2. 共享状态,存储调用存储的任务的结果,并且可以通过std::future
对象异步访问。std::packaged_task
共享状态通过调用成员函数get_future
与future
对象相关联。调用后,两个对象共享相同的共享状态:
1. 打包的任务对象是异步provider
,通过调用存储的任务,可以在某个时刻将共享状态设置为就绪。
2.future
对象是一个异步返回对象,它可以检索共享状态的值,并在必要时等待它准备就绪。
成员函数:std::packaged_task
所包含成员函数如下所示:
3.1 构造函数、get_future
std::packaged_task
构造函数如下
packaged_task() noexcept; // 1. default, 无共享状态、无任务
template <class Fn>
explicit packaged_task(Fn&& fn); // 2. init,有共享状态,任务为fn
template <class Fn, class Alloc> // 3. with allocator
explicit packaged_task(allocator_arg_t aa, const Alloc& alloc, Fn&& fn);
packaged_task(packaged_task&) = delete; // 4. copy [deleted]
packaged_task(packaged_task&& x) noexcept; // 5. move
- 上述函数声明中,
fn
为任务函数、Args
为输入参数。
future<Ret> get_future();
: 返回与对象的共享状态关联的future
对象。- 一旦调用了
packaged_task
的存储任务,返回的future
对象就可访问该任务在共享状态上设置的值或异常。
测试代码:
#include <iostream> // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::thread
int main () {
std::packaged_task<int(int)> foo; // 1. 默认构造
std::packaged_task<int(int)> bar([](int x){return x*2;}); // 2. initialized, fn为 lamda-func
foo = std::move(bar); // 3. 移动构造
std::future<int> ret = foo.get_future(); // 4. get-future
std::thread(std::move(foo),10).detach(); // 创建并、分离线程
// ...
int value = ret.get(); // 5. 阻塞,等待任务完成并返回共享状态值
std::cout << "The double of 10 is " << value << ".\n";
return 0;
}
The double of 10 is 20.
- 上述代码中
std::packaged_task<int(int)>
表示任务返回类型为int
、任务参数为一个类型为int
的参数。 - 因为返回类型为
int
,所有future
对象的类型为std::future<int>
。这两句可以该为:
using T = std::function<int(int)>; // or typedef std::function<int(int)> T;
std::packaged_task<T> foo;
3.2 operator=、operator()
- <1>
operator=
:移动赋值,获取参数对象rhs
的共享状态和存储任务。其它方面promise::operator=
相似。
packaged_task& operator= (packaged_task&& rhs) noexcept; // 1. move
packaged_task& operator= (const packaged_task&) = delete; // 2. copy [deleted]
- <2>
operator()
实现仿函数功能:
void operator()(Args... args);
operator()
可调用并执行当前对象存储的任务,将args
转发为参数:
1. 如果成功调用任务并完成,则它返回的值将存储在共享状态中。
2. 如果对存储任务的调用引发异常,则会捕获异常并将其存储在共享状态中。
在这两种情况下,共享状态都已就绪(这将取消阻止当前正在等待它的所有线程)。
共享状态可以通过相关联的future对象调用get来访问。
- <3>
reset()
:在保持相同存储任务的同时,以新的共享状态重置packaged_task
对象。
1. 这允许再次调用存储的任务。
2. 在内部,函数的行为就像move分配了一个新构造的打包任务。
测试代码:
#include <iostream> // std::cout
#include <thread> // std::thread
#include <chrono>
#include <future>
using namespace std;
void task_lambda() {
// 使用 packaged_task 包装任务
packaged_task<int(int,int)> task([](int a, int b){ return a + b;});
std::future<int> result = task.get_future(); // 1. 关联future对象
task(2, 10); // 2. 仿函数形式,启动任务
cout << "task_lambda :" << result.get() << "\n";
cout << "future.valid:" << result.valid() << endl;
task.reset(); // 3. 重置共享状态,如果没这句,下一句将报错
result = task.get_future();
cout << "future.valid:" << result.valid() << endl;
std::thread thread_(move(task), 2, 10); // 4. 通过线程启动任务,异步启动
thread_.join();
cout << "task_thread :" << result.get() << "\n";
}
int main() {
task_lambda();
return 0;
}