[C++] - C++11 多线程 - Future
整理转自:https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/tree/master/zh/chapter6-Future
Table of Contents
1 头文件摘要
1.1 头文件摘要
1.2 std::future_error 类摘要
1.3 std::promise 类摘要
1.4 std::future 类摘要
1.5 std::shared_future 类摘要
1.6 std::async 函数摘要
1.7 std::packaged_task 类摘要
2 异步任务提供者(Provider) - std::promise
2.1 std::promise 类概述
2.2 std::promise 构造函数
2.3 std::promise::get_future 介绍
2.4 std::promise::set_value 介绍
2.5 std::promise::set_exception 介绍
2.6 std::promise::set_value_at_thread_exit 介绍
2.7 std::promise::swap 介绍
3 异步任务提供者(Provider) - std::packaged_task
3.1 std::packaged_task 构造函数
3.2 std::packaged_task::valid 介绍
3.3 std::packaged_task::get_future 介绍
3.4 std::packaged_task::operator()(Args... args) 介绍
3.5 std::packaged_task::make_ready_at_thread_exit 介绍
3.6 std::packaged_task::reset() 介绍
3.7 std::packaged_task::swap() 介绍
4 Future 类型详解
4.1 std::future 详解
4.1.1 std::future 概述
4.1.2 std::future 构造函数
4.1.3 std::future::share()
4.1.4 std::future::get()
4.1.5 std::future::valid()
4.1.6 std::future::wait()
4.1.7 std::future::wait_for()
4.1.8 std::future::wait_until()
4.2 std::shared_future 介绍
4.2.1 std::shared_future 构造函数
4.2.2 std::shared_future 其他成员函数
5 与异步任务相关的类型介绍
5.1 std::future_error 介绍
5.2 其他与 std::future 相关的枚举类介绍
5.2.1 std::future_errc 类型
5.2.2 std::future_status 类型
5.2.3 std::launch 类型
6 异步任务辅助函数介绍
6.1 std::async() 函数介绍
C++11 标准中与异步任务系相关的类型主要是以下四种 std::promise
,std::packaged_task
(std::promise
,std::packaged_task
也称为异步任务的提供者 Provider,此外 std::async
也可以作为异步任务的提供者,不过 std::async
并不是类,而是函数),std::future
,std::shared_future
。另外 <future>
中还定义一些辅助的类,例如: std::future_error
,std::future_errc
,std::status
,std::launch
。
1 <future>
头文件摘要
1.1 <future>
头文件摘要
namespace std {enum class future_errc {broken_promise,future_already_retrieved,promise_already_satisfied,no_state };enum class launch : unspecified {async = unspecified,deferred = unspecified,implementation-defined};enum class future_status {ready,timeout,deferred};template <> struct is_error_code_enum<future_errc> : public true_type { };error_code make_error_code(future_errc e);error_condition make_error_condition(future_errc e);const error_category& future_category();class future_error;template <class R> class promise;template <class R> class promise<R&>;template <> class promise<void>;template <class R>void swap(promise<R>& x, promise<R>& y);template <class R, class Alloc>struct uses_allocator<promise<R>, Alloc>;template <class R> class future;template <class R> class future<R&>;template <> class future<void>;template <class R> class shared_future;template <class R> class shared_future<R&>;template <> class shared_future<void>;template <class> class packaged_task; // undefinedtemplate <class R, class... ArgTypes>class packaged_task<R(ArgTypes...)>;template <class R>void swap(packaged_task<R(ArgTypes...)&, packaged_task<R(ArgTypes...)>&);template <class R, class Alloc>struct uses_allocator<packaged_task<R>, Alloc>;template <class F, class... Args>future<typename result_of<F(Args...)>::type>async(F&& f, Args&&... args);template <class F, class... Args>future<typename result_of<F(Args...)>::type>async(launch policy, F&& f, Args&&... args);
}
1.2 std::future_error
类摘要
namespace std {class future_error : public logic_error {public:future_error(error_code ec); // exposition onlyconst error_code& code() const noexcept;const char* what() const noexcept;};
}
1.3 std::promise
类摘要
namespace std {template <class R>class promise {public:promise();template <class Allocator>promise(allocator_arg_t, const Allocator& a);promise(promise&& rhs) noexcept;promise(const promise& rhs) = delete;~promise();// assignmentpromise& operator=(promise&& rhs) noexcept;promise& operator=(const promise& rhs) = delete;void swap(promise& other) noexcept;// retrieving the resultfuture<R> get_future();// setting the resultvoid set_value(see below);void set_exception(exception_ptr p);// setting the result with deferred notificationvoid set_value_at_thread_exit(const R& r);void set_value_at_thread_exit(see below);void set_exception_at_thread_exit(exception_ptr p);};template <class R>void swap(promise<R>& x, promise<R>& y);template <class R, class Alloc>struct uses_allocator<promise<R>, Alloc>;
}
1.4 std::future
类摘要
namespace std {template <class R>class future {public:future();future(future &&);future(const future& rhs) = delete;~future();future& operator=(const future& rhs) = delete;future& operator=(future&&) noexcept;shared_future<R> share() &&;// retrieving the valuesee below get();// functions to check statebool valid() const;void wait() const;template <class Rep, class Period>future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const;template <class Clock, class Duration>future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;};
}
1.5 std::shared_future
类摘要
namespace std {template <class R>class shared_future {public:shared_future() noexcept;shared_future(const shared_future& rhs);shared_future(future<R>&&) noexcept;shared_future(shared_future&& rhs) noexcept;~shared_future();shared_future& operator=(const shared_future& rhs);shared_future& operator=(shared_future&& rhs);// retrieving the valuesee below get() const;// functions to check statebool valid() const;void wait() const;template <class Rep, class Period>future_status wait_for(const chrono::duration<Rep, Period>& rel_time) const;template <class Clock, class Duration>future_status wait_until(const chrono::time_point<Clock, Duration>& abs_time) const;};
}
1.6 std::async
函数摘要
namespace std {template <class F, class... Args>future<typename result_of<F(Args...)>::type>async(F&& f, Args&&... args);template <class F, class... Args>future<typename result_of<F(Args...)>::type>async(launch policy, F&& f, Args&&... args);
}
1.7 std::packaged_task
类摘要
namespace std {template<class> class packaged_task; // undefinedtemplate<class R, class... ArgTypes>class packaged_task<R(ArgTypes...)> {public:typedef R result_type;// construction and destructionpackaged_task() noexcept;template <class F>explicit packaged_task(F f);template <class F, class Allocator>explicit packaged_task(allocator_arg_t, const Allocator& a, F f);explicit packaged_task(R(*f)(ArgTypes...));template <class F>explicit packaged_task(F&& f);template <class F, class Allocator>explicit packaged_task(allocator_arg_t, const Allocator& a, F&& f);~packaged_task();// no copypackaged_task(packaged_task&) = delete;packaged_task& operator=(packaged_task&) = delete;// move supportpackaged_task(packaged_task&& other) noexcept;packaged_task& operator=(packaged_task&& other);void swap(packaged_task& other) noexcept;bool valid() const noexcept;// result retrievalfuture<R> get_future();// executionvoid operator()(ArgTypes... );void make_ready_at_thread_exit(ArgTypes...);void reset();};template <class R, class... ArgTypes>void swap(packaged_task<R(ArgTypes...)>& x, packaged_task<R(ArgTypes...)>& y) noexcept;template <class R, class Alloc>struct uses_allocator<packaged_task<R>, Alloc>;
}
<future>
头文件中包含了以下几个类和函数:
- Providers 类:
std::promise
,std::package_task
- Futures 类:
std::future
,std::shared_future
. - Providers 函数:
std::async()
- 其他类型:
std::future_error
,std::future_errc
,std::future_status
,std::launch
.
2 异步任务提供者(Provider) - std::promise
2.1 std::promise
类概述
Promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。在 promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future
)上保存一个类型为 T 的值。
可以通过 get_future
来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态(shared state)
- promise 对象是异步 Provider,它可以在某一时刻设置共享状态的值。
- future 对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为 ready,然后才能获取共享状态的值。
下面以一个简单的例子来说明上述关系:
#include <iostream> // std::cout
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::futurevoid print_int(std::future<int>& fut) {int x = fut.get(); // 获取共享状态的值.std::cout << "value: " << x << '\n'; // 打印 value: 10.
}int main ()
{std::promise<int> prom; // 生成一个 std::promise<int> 对象.std::future<int> fut = prom.get_future(); // 和 future 关联.std::thread t(print_int, std::ref(fut)); // 将 future 交给另外一个线程t.prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步.t.join();return 0;
}
2.2 std::promise
构造函数
default (1) | promise(); |
---|---|
with allocator (2) | template <class Alloc> promise (allocator_arg_t aa, const Alloc& alloc); |
copy [deleted] (3) | promise (const promise&) = delete; |
move (4) | promise (promise&& x) noexcept; |
- 默认构造函数,初始化一个空的共享状态。
- 带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态。
- 拷贝构造函数,被禁用。
- 移动构造函数。
另外,std::promise
的 operator=
没有拷贝语义,即 std::promise
普通的赋值操作被禁用,operator=
只有 move 语义,所以 std::promise
对象是禁止拷贝的。
例子:
#include <iostream> // std::cout
#include <thread> // std::thread
#include <future> // std::promise, std::futurestd::promise<int> prom;void print_global_promise () {std::future<int> fut = prom.get_future();int x = fut.get();std::cout << "value: " << x << '\n';
}int main ()
{std::thread th1(print_global_promise);prom.set_value(10);th1.join();prom = std::promise<int>(); // prom 被move赋值为一个新的 promise 对象.std::thread th2 (print_global_promise);prom.set_value (20);th2.join();return 0;
}
2.3 std::promise::get_future
介绍
该函数返回一个与 promise 共享状态相关联的 future 。返回的 future 对象可以访问由 promise 对象设置在共享状态上的值或者某个异常对象。只能从 promise 共享状态获取一个 future 对象。在调用该函数之后,promise 对象通常会在某个时间点准备好(设置一个值或者一个异常对象),如果不设置值或者异常,promise 对象在析构时会自动地设置一个 future_error
异常(broken_promise
)来设置其自身的准备状态。上面的例子中已经提到了 get_future
,此处不再重复。
2.4 std::promise::set_value
介绍
generic template (1) |
|
---|---|
specializations (2) |
|
设置共享状态的值,此后 promise 的共享状态标志变为 ready
.
2.5 std::promise::set_exception
介绍
为 promise 对象设置异常,此后 promise 的共享状态变标志变为 ready
,例子如下,线程1从终端接收一个整数,线程 2 将该整数打印出来,如果线程 1 接收一个非整数,则为 promise 设置一个异常(failbit) ,线程 2 在 std::future::get
时抛出该异常。
#include <iostream> // std::cin, std::cout, std::ios#include <functional> // std::ref#include <thread> // std::thread#include <future> // std::promise, std::future#include <exception> // std::exception, std::current_exceptionvoid get_int(std::promise<int>& prom) {int x;std::cout << "Please, enter an integer value: ";std::cin.exceptions (std::ios::failbit); // throw on failbittry {std::cin >> x; // sets failbit if input is not intprom.set_value(x);} catch (std::exception&) {prom.set_exception(std::current_exception());}}void print_int(std::future<int>& fut) {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(get_int, std::ref(prom));std::thread th2(print_int, std::ref(fut));th1.join();th2.join();return 0;}
2.6 std::promise::set_value_at_thread_exit
介绍
设置共享状态的值,但是不将共享状态的标志设置为 ready
,当线程退出时该 promise 对象会自动设置为 ready。如果某个 std::future
对象与该 promise 对象的共享状态相关联,并且该 future 对象正在调用 get
,则调用 get 的线程会被阻塞,当线程退出时,调用 future::get
的线程解除阻塞,同时 get
返回 set_value_at_thread_exit
所设置的值。注意,该函数已经设置了 promise 共享状态的值,如果在线程结束之前有其他设置或者修改共享状态的值的操作,则会抛出 future_error
( promise_already_satisfied
)。
2.7 std::promise::swap
介绍
交换 promise 的共享状态。
3 异步任务提供者(Provider) - std::packaged_task
本文主要介绍 std::packaged_task
。
std::packaged_task
包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task
与 std::function
类似,只不过 std::packaged_task
将其包装的可调用对象的执行结果传递给一个 std::future
对象(该对象通常在另外一个线程中获取 std::packaged_task
任务的执行结果)。
std::packaged_task
对象内部包含了两个最基本元素,一、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,二、共享状态(shared state),用于保存任务的返回值,可以通过 std::future
对象来达到异步访问共享状态的效果。
可以通过 std::packged_task::get_future
来获取与共享状态相关联的 std::future
对象。在调用该函数之后,两个对象共享相同的共享状态,具体解释如下:
std::packaged_task
对象是异步 Provider,它在某一时刻通过调用被包装的任务来设置共享状态的值。std::future
对象是一个异步返回对象,通过它可以获得共享状态的值,当然在必要的时候需要等待共享状态标志变为ready
.
std::packaged_task
的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止。下面一个小例子大致讲了 std::packaged_task
的用法:
#include <iostream> // std::cout
#include <future> // std::packaged_task, std::future
#include <chrono> // std::chrono::seconds
#include <thread> // std::thread, std::this_thread::sleep_for// count down taking a second for each value:
int countdown (int from, int to) {for (int i=from; i!=to; --i) {std::cout << i << '\n';std::this_thread::sleep_for(std::chrono::seconds(1));}std::cout << "Finished!\n";return from - to;
}int main ()
{std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_taskstd::future<int> ret = task.get_future(); // 获得与 packaged_task 共享状态相关联的 future 对象.std::thread th(std::move(task), 10, 0); //创建一个新线程完成计数任务.int value = ret.get(); // 等待任务完成并获取结果.std::cout << "The countdown lasted for " << value << " seconds.\n";th.join();return 0;
}
执行结果为:
10
9
8
7
6
5
4
3
2
1
Finished!
the countdown lasted for 10 seconds.
3.1 std::packaged_task
构造函数
default (1) | packaged_task() noexcept; |
---|---|
initialization (2) |
|
with allocator (3) |
|
copy [deleted] (4) | packaged_task (const packaged_task&) = delete; |
move (5) | packaged_task (packaged_task&& x) noexcept; |
std::packaged_task
构造函数共有 5 中形式,不过拷贝构造已经被禁用了。下面简单地介绍一下上述几种构造函数的语义:
- 默认构造函数,初始化一个空的共享状态,并且该
packaged_task
对象无包装任务。 - 初始化一个共享状态,并且被包装任务由参数 fn 指定。
- 带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态。
- 拷贝构造函数,被禁用。
- 移动构造函数。
下面例子介绍了各类构造函数的用法:
#include <iostream> // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::threadint main ()
{std::packaged_task<int(int)> foo; // 默认构造函数.// 使用 lambda 表达式初始化一个 packaged_task 对象.std::packaged_task<int(int)> bar([](int x){return x*2;});foo = std::move(bar); // move-赋值操作,也是 C++11 中的新特性.// 获取与 packaged_task 共享状态相关联的 future 对象.std::future<int> ret = foo.get_future();std::thread(std::move(foo), 10).detach(); // 产生线程,调用被包装的任务.int value = ret.get(); // 等待任务完成并获取结果.std::cout << "The double of 10 is " << value << ".\n";return 0;
}
与 std::promise
类似, std::packaged_task
也禁用了普通的赋值操作运算,只允许 move
赋值运算。
3.2 std::packaged_task::valid
介绍
检查当前 packaged_task
是否和一个有效的共享状态相关联,对于由默认构造函数生成的 packaged_task
对象,该函数返回 false
,除非中间进行了 move
赋值操作或者 swap
操作。
请看下例:
#include <iostream> // std::cout#include <utility> // std::move#include <future> // std::packaged_task, std::future#include <thread> // std::thread// 在新线程中启动一个 int(int) packaged_task.std::future<int> launcher(std::packaged_task<int(int)>& tsk, int arg){if (tsk.valid()) {std::future<int> ret = tsk.get_future();std::thread (std::move(tsk),arg).detach();return ret;}else return std::future<int>();}int main (){std::packaged_task<int(int)> tsk([](int x){return x*2;});std::future<int> fut = launcher(tsk,25);std::cout << "The double of 25 is " << fut.get() << ".\n";return 0;}
3.3 std::packaged_task::get_future
介绍
返回一个与 packaged_task
对象共享状态相关的 future
对象。返回的 future
对象可以获得由另外一个线程在该 packaged_task
对象的共享状态上设置的某个值或者异常。
请看例子(其实前面已经讲了 get_future
的例子):
#include <iostream> // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::threadint main ()
{std::packaged_task<int(int)> tsk([](int x) { return x * 3; })); // package taskstd::future<int> fut = tsk.get_future(); // 获取 future 对象.std::thread(std::move(tsk), 100).detach(); // 生成新线程并调用packaged_task.int value = fut.get(); // 等待任务完成, 并获取结果.std::cout << "The triple of 100 is " << value << ".\n";return 0;
}
3.4 std::packaged_task::operator()(Args... args)
介绍
调用该 packaged_task 对象所包装的对象(通常为函数指针,函数对象,lambda 表达式等),传入的参数为 args. 调用该函数一般会发生两种情况:
- 如果成功调用
packaged_task
所包装的对象,则返回值(如果被包装的对象有返回值的话)被保存在packaged_task
的共享状态中。 - 如果调用
packaged_task
所包装的对象失败,并且抛出了异常,则异常也会被保存在packaged_task
的共享状态中。
以上两种情况都使共享状态的标志变为 ready
,因此其他等待该共享状态的线程可以获取共享状态的值或者异常并继续执行下去。
共享状态的值可以通过在 future 对象(由 get_future
获得)上调用 get
来获得。
由于被包装的任务在 packaged_task
构造时指定,因此调用 operator()
的效果由 packaged_task 对象构造时所指定的可调用对象来决定:
- 如果被包装的任务是函数指针或者函数对象,调用
std::packaged_task::operator()
只是将参数传递给被包装的对象。 - 如果被包装的任务是指向类的非静态成员函数的指针,那么
std::packaged_task::operator()
的第一个参数应该指定为成员函数被调用的那个对象,剩余的参数作为该成员函数的参数。 - 如果被包装的任务是指向类的非静态成员变量,那么
std::packaged_task::operator()
只允许单个参数。
3.5 std::packaged_task::make_ready_at_thread_exit
介绍
该函数会调用被包装的任务,并向任务传递参数,类似 std::packaged_task
的 operator()
成员函数。但是与 operator()
函数不同的是,make_ready_at_thread_exit
并不会立即设置共享状态的标志为 ready
,而是在线程退出时设置共享状态的标志。
如果与该 packaged_task 共享状态相关联的 future 对象在 future::get
处等待,则当前的 future::get
调用会被阻塞,直到线程退出。而一旦线程退出,future::get
调用继续执行,或者抛出异常。
注意,该函数已经设置了 promise 共享状态的值,如果在线程结束之前有其他设置或者修改共享状态的值的操作,则会抛出 future_error
( promise_already_satisfied
)。
3.6 std::packaged_task::reset()
介绍
重置 packaged_task 的共享状态,但是保留之前的被包装的任务。请看例子,该例子中,packaged_task 被重用了多次:
#include <iostream> // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::thread// a simple task:
int triple (int x) { return x*3; }int main ()
{std::packaged_task<int(int)> tsk (triple); // package taskstd::future<int> fut = tsk.get_future();std::thread (std::move(tsk), 100).detach();std::cout << "The triple of 100 is " << fut.get() << ".\n";// re-use same task object:tsk.reset();fut = tsk.get_future();std::thread(std::move(tsk), 200).detach();std::cout << "Thre triple of 200 is " << fut.get() << ".\n";return 0;
}
3.7 std::packaged_task::swap()
介绍
交换 packaged_task
的共享状态。
4 Future 类型详解
本文主要介绍 std::future
,std::shared_future
以及 std::future_error
,另外还会介绍 <future>
头文件中的 std::async
,std::future_category
函数以及相关枚举类型。
4.1 std::future
详解
4.1.1 std::future
概述
前面已经多次提到过 std::future
,那么 std::future
究竟是什么呢?简单地说,std::future
可以用来获取异步任务的结果,因此可以把它当成一种简单的线程间同步的手段。std::future
通常由某个 Provider 创建,你可以把 Provider 想象成一个异步任务的提供者,Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future
对象调用 get
(通常在另外一个线程中) 获取该值,如果共享状态的标志不为 ready
,则调用 std::future::get
会阻塞当前的调用者,直到 Provider 设置了共享状态的值(此时共享状态的标志变为 ready
),std::future::get
返回异步任务的值或异常(如果发生了异常)。
一个有效(valid
)的 std::future
对象通常由以下三种 Provider 创建,并和某个共享状态相关联。Provider 可以是函数或者类,其实我们前面都已经提到了,他们分别是:
std::async
函数,本文后面会介绍std::async()
函数。std::promise::get_future
,get_future
为 promise 类的成员函数,详见 C++11 并发指南四(<future>
详解一std::promise
介绍)。std::packaged_task::get_future
,此时get_future
为packaged_task
的成员函数,详见C++11 并发指南四(<future>
详解二std::packaged_task
介绍)。
一个 std::future
对象只有在有效(valid
)的情况下才有用(useful),由 std::future
默认构造函数创建的 future
对象不是有效的(除非当前非有效的 future
对象被 move
赋值另一个有效的 future 对象)。
在一个有效的 future
对象上调用 get
会阻塞当前的调用者,直到 Provider 设置了共享状态的值或异常(此时共享状态的标志变为 ready
),std::future::get
将返回异步任务的值或异常(如果发生了异常)。
下面以一个简单的例子说明上面一段文字吧(参考):
// future example
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <chrono> // std::chrono::milliseconds// a non-optimized way of checking for prime numbers:
bool is_prime(int x)
{for (int i = 2; i < x; ++i)if (x % i == 0)return false;return true;
}int main()
{// call function asynchronously:std::future < bool > fut = std::async(is_prime, 444444443);// do something while waiting for function to set future:std::cout << "checking, please wait";std::chrono::milliseconds span(100);while (fut.wait_for(span) == std::future_status::timeout)std::cout << '.';bool x = fut.get(); // retrieve return valuestd::cout << "\n444444443 " << (x ? "is" : "is not") << " prime.\n";return 0;
}
4.1.2 std::future
构造函数
std::future
一般由 std::async
, std::promise::get_future
, std::packaged_task::get_future
创建,不过也提供了构造函数,如下表所示:
default (1) | future() noexcept; |
---|---|
copy [deleted] (2) | future(const future&) = delete; |
move (3) | future(future&& x) noexcept; |
不过 std::future
的拷贝构造函数是被禁用的,只提供了默认的构造函数和 move
构造函数(注:C++ 新特性)。另外,std::future
的普通赋值操作也被禁用,只提供了 move
赋值操作。如下代码所示:
std::future<int> fut; // 默认构造函数
fut = std::async(do_some_task); // move-赋值操作
4.1.3 std::future::share()
返回一个 std::shared_future
对象(本文后续内容将介绍 std::shared_future
),调用该函数之后,该 std::future
对象本身已经不和任何共享状 态相关联,因此该 std::future
的状态不再是 valid
的了。
#include <iostream> // std::cout
#include <future> // std::async, std::future, std::shared_futureint do_get_value() { return 10; }int main ()
{std::future<int> fut = std::async(do_get_value);std::shared_future<int> shared_fut = fut.share();// 共享的 future 对象可以被多次访问.std::cout << "value: " << shared_fut.get() << '\n';std::cout << "its double: " << shared_fut.get()*2 << '\n';return 0;
}
4.1.4 std::future::get()
std::future::get
一共有三种形式,如下表所示:
generic template (1) | T get(); |
---|---|
reference specialization (2) | R& future<R&>::get(); // when T is a reference type (R&) |
void specialization (3) | void future<void>::get(); // when T is void |
当与该 std::future
对象相关联的共享状态标志变为 ready
后,调用该函数将返回保存在共享状态中的值,如果共享状态的标志不为 ready
,则调用该函数会阻塞当前的调用者,而此后一旦共享状态的标志变为 ready
,get
返回 Provider 所设置的共享状态的值或者异常(如果抛出了异常)。
请看下面的程序:
#include <iostream> // std::cin, std::cout, std::ios
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future
#include <exception> // std::exception, std::current_exceptionvoid get_int(std::promise<int>& prom) {int x;std::cout << "Please, enter an integer value: ";std::cin.exceptions (std::ios::failbit); // throw on failbittry {std::cin >> x; // sets failbit if input is not intprom.set_value(x);} catch (std::exception&) {prom.set_exception(std::current_exception());}
}void print_int(std::future<int>& fut) {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(get_int, std::ref(prom));std::thread th2(print_int, std::ref(fut));th1.join();th2.join();return 0;
}
4.1.5 std::future::valid()
检查当前的 std::future
对象是否有效,即释放与某个共享状态相关联。
一个有效的 std::future
对象只能通过 std::async()
, std::future::get_future
或者 std::packaged_task::get_future
来初始化。
另外由 std::future
默认构造函数创建的 std::future
对象是无效(invalid
)的,当然通过 std::future
的 move
赋值后该 std::future
对象也可以变为 valid
。
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <utility> // std::moveint do_get_value() { return 11; }int main ()
{// 由默认构造函数创建的 std::future 对象,// 初始化时该 std::future 对象处于为 invalid 状态.std::future<int> foo, bar;foo = std::async(do_get_value); // move 赋值, foo 变为 valid.bar = std::move(foo); // move 赋值, bar 变为 valid, 而 move 赋值以后 foo 变为 invalid.if (foo.valid())std::cout << "foo's value: " << foo.get() << '\n';elsestd::cout << "foo is not valid\n";if (bar.valid())std::cout << "bar's value: " << bar.get() << '\n';elsestd::cout << "bar is not valid\n";return 0;
}
4.1.6 std::future::wait()
等待与当前 std::future
对象相关联的共享状态的标志变为 ready
.
如果共享状态的标志不是 ready
(此时 Provider 没有在共享状态上设置值(或者异常)),调用该函数会被阻塞当前线程,直到共享状态的标志变为 ready。
一旦共享状态的标志变为 ready
,wait()
函数返回,当前线程被解除阻塞,但是 wait()
并不读取共享状态的值或者异常。下面的代码说明了 std::future::wait()
的用法
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <chrono> // std::chrono::milliseconds// a non-optimized way of checking for prime numbers:
bool do_check_prime(int x) // 为了体现效果, 该函数故意没有优化.
{for (int i = 2; i < x; ++i)if (x % i == 0)return false;return true;
}int main()
{// call function asynchronously:std::future < bool > fut = std::async(do_check_prime, 194232491);std::cout << "Checking...\n";fut.wait();std::cout << "\n194232491 ";if (fut.get()) // guaranteed to be ready (and not block) after wait returnsstd::cout << "is prime.\n";elsestd::cout << "is not prime.\n";return 0;
}
执行结果如下:
concurrency ) ./Future-wait
Checking...194232491 is prime.
concurrency )
4.1.7 std::future::wait_for()
与 std::future::wait()
的功能类似,即等待与该 std::future
对象相关联的共享状态的标志变为 ready
,该函数原型如下:
template <class Rep, class Period>
future_status wait_for (const chrono::duration<Rep,Period>& rel_time) const;
而与 std::future::wait()
不同的是,wait_for()
可以设置一个时间段 rel_time
,如果共享状态的标志在该时间段结束之前没有被 Provider 设置为 ready
,则调用 wait_for
的线程被阻塞,在等待了 rel_time
的时间长度后 wait_for()
返回,返回值如下:
返回值 | 描述 |
---|---|
future_status::ready | 共享状态的标志已经变为 ready,即 Provider 在共享状态上设置了值或者异常。 |
future_status::timeout | 超时,即在规定的时间内共享状态的标志没有变为 ready。 |
future_status::deferred | 共享状态包含一个 deferred 函数。 |
请看下面的例子:
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <chrono> // std::chrono::milliseconds// a non-optimized way of checking for prime numbers:
bool do_check_prime(int x) // 为了体现效果, 该函数故意没有优化.
{for (int i = 2; i < x; ++i)if (x % i == 0)return false;return true;
}int main()
{// call function asynchronously:std::future < bool > fut = std::async(do_check_prime, 194232491);std::cout << "Checking...\n";std::chrono::mi lliseconds span(1000); // 设置超时间隔.// 如果超时,则输出".",继续等待while (fut.wait_for(span) == std::future_status::timeout)std::cout << '.';std::cout << "\n194232491 ";if (fut.get()) // guaranteed to be ready (and not block) after wait returnsstd::cout << "is prime.\n";elsestd::cout << "is not prime.\n";return 0;
}
4.1.8 std::future::wait_until()
与 std::future::wait()
的功能类似,即等待与该 std::future
对象相关联的共享状态的标志变为 ready
,该函数原型如下:
template <class Rep, class Period>
future_status wait_until (const chrono::time_point<Clock,Duration>& abs_time) const;
而与 std::future::wait()
不同的是,wait_until()
可以设置一个系统绝对时间点 abs_time
,如果共享状态的标志在该时间点到来之前没有被 Provider 设置为 ready
,则调用 wait_until
的线程被阻塞,在 abs_time
这一时刻到来之后 wait_until()
返回,返回值如下:
返回值 | 描述 |
---|---|
future_status::ready | 共享状态的标志已经变为 ready,即 Provider 在共享状态上设置了值或者异常。 |
future_status::timeout | 超时,即在规定的时间内共享状态的标志没有变为 ready。 |
future_status::deferred | 共享状态包含一个 deferred 函数。 |
4.2 std::shared_future
介绍
std::shared_future
与 std::future
类似,但是 std::shared_future
可以拷贝、多个 std::shared_future
可以共享某个共享状态的最终结果(即共享状态的某个值或者异常)。shared_future
可以通过某个 std::future
对象隐式转换(参见 std::shared_future
的构造函数),或者通过 std::future::share()
显示转换,无论哪种转换,被转换的那个 std::future
对象都会变为 not-valid.
4.2.1 std::shared_future
构造函数
std::shared_future
共有四种构造函数,如下表所示:
default (1) | shared_future() noexcept; |
---|---|
copy (2) | shared_future (const shared_future& x); |
move (3) | shared_future (shared_future&& x) noexcept; |
move from future (4) | shared_future (future<T>&& x) noexcept; |
最后 move from future(4)
即从一个有效的 std::future
对象构造一个 std::shared_future
,构造之后 std::future
对象 x 变为无效(not-valid)。
4.2.2 std::shared_future
其他成员函数
std::shared_future
的成员函数和 std::future
大部分相同,如下(每个成员函数都给出了连接):
operator=()
: 赋值操作符,与 std::future 的赋值操作不同,std::shared_future 除了支持 move 赋值操作外,还支持普通的赋值操作。get()
: 获取与该 std::shared_future 对象相关联的共享状态的值(或者异常)。valid()
: 有效性检查。wait()
: 等待与该 std::shared_future 对象相关联的共享状态的标志变为 ready。wait_for()
: 等待与该 std::shared_future 对象相关联的共享状态的标志变为 ready。(等待一段时间,超过该时间段wait_for 返回。)wait_until()
: 等待与该 std::shared_future 对象相关联的共享状态的标志变为 ready。(在某一时刻前等待,超过该时刻 wait_until 返回。)
5 与异步任务相关的类型介绍
5.1 std::future_error
介绍
class future_error : public logic_error;
std::future_error
继承子 C++ 标准异常体系中的 logic_error
,有关 C++ 异常的继承体系,请参考相关的C++教程 ;-)。
5.2 其他与 std::future
相关的枚举类介绍
下面介绍与 std::future
相关的枚举类型。与 std::future
相关的枚举类型包括:
enum class future_errc;
enum class future_status;
enum class launch;
下面分别介绍以上三种枚举类型:
5.2.1 std::future_errc
类型
std::future_errc
类型描述如下:
类型 |
取值
|
描述 |
---|---|---|
broken_promise |
0
|
与该 std::future 共享状态相关联的 std::promise 对象在设置值或者异常之前已被销毁。 |
future_already_retrieved |
1
|
与该 std::future 对象相关联的共享状态的值已经被当前 Provider 获取了,即调用了 std::future::get 函数。 |
promise_already_satisfied |
2
|
std::promise 对象已经对共享状态设置了某一值或者异常。 |
no_state |
3
|
无共享状态。 |
5.2.2 std::future_status
类型
std::future_status
类型主要用在 std::future
(或 std::shared_future
)中的 wait_for
和 wait_until
两个函数中的。
类型 |
取值
|
描述 |
---|---|---|
future_status::ready |
0
|
`wait_for`(`或 wait_until`) 因为共享状态的标志变为 ready 而返回。 |
future_status::timeout |
1
|
超时,即 `wait_for`(`或 wait_until`) 因为在指定的时间段(或时刻)内共享状态的标志依然没有变为 `ready` 而返回。 |
future_status::deferred |
2
|
共享状态包含了 deferred 函数。 |
5.2.3 std::launch
类型
该枚举类型主要是在调用 std::async
设置异步任务的启动策略的。
类型 | 描述 |
---|---|
launch::async | Asynchronous: 异步任务会在另外一个线程中调用,并通过共享状态返回异步任务的结果(一般是调用 std::future::get() 获取异步任务的结果)。 |
launch::deferred | Deferred: 异步任务将会在共享状态被访问时调用,相当于按需调用(即延迟(deferred)调用)。 |
请看下例(参考):
#include <iostream> // std::cout
#include <future> // std::async, std::future, std::launch
#include <chrono> // std::chrono::milliseconds
#include <thread> // std::this_thread::sleep_forvoid do_print_ten(char c, int ms)
{for (int i = 0; i < 10; ++i) {std::this_thread::sleep_for(std::chrono::milliseconds(ms));std::cout << c;}
}int main()
{std::cout << "with launch::async:\n";std::future < void >foo =std::async(std::launch::async, do_print_ten, '*', 100);std::future < void >bar =std::async(std::launch::async, do_print_ten, '@', 200);// async "get" (wait for foo and bar to be ready):foo.get();bar.get();std::cout << "\n\n";std::cout << "with launch::deferred:\n";foo = std::async(std::launch::deferred, do_print_ten, '*', 100);bar = std::async(std::launch::deferred, do_print_ten, '@', 200);// deferred "get" (perform the actual calls):foo.get();bar.get();std::cout << '\n';return 0;
}
在机器上执行结果:
with launch::async:
*@**@**@**@**@*@@@@@with launch::deferred:
**********@@@@@@@@@@
6 异步任务辅助函数介绍
6.1 std::async()
函数介绍
与 std::future
相关的函数主要是 std::async()
,原型如下:
unspecified policy (1) |
template <class Fn, class... Args> future<typename result_of<Fn(Args...)>::type> async(Fn&& fn, Args&&... args); |
---|---|
specific policy (2) |
template <class Fn, class... Args> future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn, Args&&... args); |
上面两组 std::async()
的不同之处是第一类 std::async
没有指定异步任务(即执行某一函数)的启动策略(launch policy),而第二类函数指定了启动策略,详见 std::launch
枚举类型,指定启动策略的函数的 policy 参数可以是 launch::async
,launch::deferred
,以及两者的按位或( | )。
std::async()
的 fn
和 args
参数用来指定异步任务及其参数。另外,std::async()
返回一个 std::future
对象,通过该对象可以获取异步任务的值或异常(如果异步任务抛出了异常)。
下面介绍一下 std::async
的用法。
#include <stdio.h>
#include <stdlib.h>#include <cmath>
#include <chrono>
#include <future>
#include <iostream>double ThreadTask(int n) {std::cout << std::this_thread::get_id()<< " start computing..." << std::endl;double ret = 0;for (int i = 0; i <= n; i++) {ret += std::sin(i);}std::cout << std::this_thread::get_id()<< " finished computing..." << std::endl;return ret;
}int main(int argc, const char *argv[])
{std::future<double> f(std::async(std::launch::async, ThreadTask, 100000000));#if 0while(f.wait_until(std::chrono::system_clock::now() + std::chrono::seconds(1))!= std::future_status::ready) {std::cout << "task is running...\n";}
#elsewhile(f.wait_for(std::chrono::seconds(1))!= std::future_status::ready) {std::cout << "task is running...\n";}
#endifstd::cout << f.get() << std::endl;return EXIT_SUCCESS;
}
[C++] - C++11 多线程 - Future相关推荐
- C++11多线程---future、shared_future、atomic
目录 一.std::future的其他成员函数 二.std::shared_future 三.原子操作std::atmic 在上篇:C++11多线程---async.future.package_ta ...
- C++11多线程のfuture,promise,package_task
一.c++11中可以在调用进程中获取被调进程中的结果,具体用法如下 // threadTest.cpp: 定义控制台应用程序的入口点. // #include "stdafx.h" ...
- C++11多线程---future和promise
future和promise的作用是在不同线程之间传递数据.使用指针也可以完成数据的传递,但是指针非常危险,因为互斥量不能阻止指针的访问:而且指针的方式传递的数据是固定的,如果更改数据类型,那么还需要 ...
- C++11 并发指南一(C++11 多线程初探)
引言 C++11 自2011年发布以来已经快两年了,之前一直没怎么关注,直到最近几个月才看了一些 C++11 的新特性,今后几篇博客我都会写一些关于 C++11 的特性,算是记录一下自己学到的东西吧, ...
- C++11 多线程库使用说明
多线程基础 1.1 进程与线程 根本区别: 进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位 开销方面: 每个进程都有自己独立的代码和数据空间,程序之间的切换开销较大. 线程可以看作是 ...
- [C++] - C++11 多线程 - Thread
转载整理自:https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/tree/master/zh/chapter3-Thread 1 ...
- 【转】C++11多线程的基本使用
[转]C++11多线程的基本使用 来自:https://blog.csdn.net/wrx1721267632/article/details/52197849/ posted on 2018-08- ...
- Linux与C++11多线程编程(学习笔记)
多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #inc ...
- C++11 并发指南九(综合运用: C++11 多线程下生产者消费者模型详解)
前面八章介绍了 C++11 并发编程的基础(抱歉哈,第五章-第八章还在草稿中),本文将综合运用 C++11 中的新的基础设施(主要是多线程.锁.条件变量)来阐述一个经典问题--生产者消费者模型,并给出 ...
最新文章
- 9月推荐 | 精选机器学习文章Top10
- 斯坦福大学报告称中国AI论文引用率首超美国!但李国杰院士也发文灵魂拷问...
- oneshot单样本学习笔记
- 剑指Offer_14_链表中倒数第k个结点
- python 获取子目录下的所有文件的路径
- PtQt4标准对话框——QFileDialog
- SCOPE_IDENTITY 和 @@IDENTITY 的区别
- 对象的序列化流_ObjectOutputStream
- 说说 ES6 的 let 指令
- 华为手机8.0.0怎么找到云相册_华为G9怎么找到云相册_失而复得 华为“查找手机”功能有多强大?快来了解一下......
- 入侵检测系统的性能的辨别(3)
- 2020未来科学大奖获奖名单公布 张亭栋、王振义等获奖
- 实录:VCS双机使用DiskReservation资源导致多路径失效
- VS C++ vector结构体 增加元素 删除元素 获取第一个元素 最后一个元素 清空元素
- android国际化多语言对照
- 【1】PASCAL VOC数据集下载
- ueditor修改默认图片保存路径,ueditor根据楼盘ID保存图片路径
- 自定义控件之仿电话簿汉字排序侧边拼音
- 存进销系统 c语言大作业,c语言,程序设计大题,*纳税以系统(2)求法,求解...
- Png图片换色的方法
热门文章
- 解决centos使用nc命令报错:Ncat: Connection refused.
- 计算机管理在哪个里面,在计算机中哪个子系统是其他子系统的管理者
- mysql表增加一行_数据库表增加一行数据
- numeric转换varchar_将数据类型varchar转换为numeric时选择失败
- flutter 图解_Flutter 54: 图解基本生命周期
- C#关键字详解第一节
- beego2---入门
- 去除input填充颜色
- [洛谷2397]yyy loves Maths VI
- treeview 文字垂直方向_word文本排版技巧:改变文字方向的这些方法,你知道吗?...