前言
条件变量在多个线程等待同一个事件的场合比较有用,如果等待线程只打算等待一次,那么当条件为
true
时它就不在等待这个条件变量了,条件变量未必是同步机制的最佳选择,在这个场景下使用期值(future) 可能会更合适,C++ 标准库使用std::future
为这类一次性事件建模, 将这种一次性事件称为期望(future)。std::future
通常与std::async
、std::packaged_task
或std::promise
一起使用。
一、std::future
1.1、模版声明
在C++多线程编程中,同步线程间的操作通常是一个关键问题。C++11引入了
std::future
模版,提供了一种获取异步调用结果的机制,即:使用std::future
对象表示异步操作的结果,模版声明如下:
template< class T > class future;
1.2、成员方法
constructor
:构造函数、创建一个std::future
对象1)
不带参数的默认构造函数,此对象没有共享状态,因此它是无效的,但是可以通过移动赋值的方式将一个有效的future值赋值给它2)
禁用拷贝构造3)
支持移动构造
destructor
:析构函数,释放std::future
对象operator=
:移动future
对象1)
禁用拷贝赋值2)
支持移动赋值
get()
:获取异步调用的结果1)
当共享状态就绪时,返回存储在共享状态中的值(或抛出异常)2)
如果共享状态尚未就绪(即提供者尚未设置其值或异常),则该函数将阻塞调用的线程直到就绪3)
当共享状态就绪后,则该函数将取消阻塞并返回或抛出异常释放其共享状态,这使得future
对象不再有效,因此对于每一个future
共享状态,该函数最多应被调用一次4)
共享状态是作为原子操作被访问
wait()
:等待异步调用返回结果1)
等待共享状态就绪2)
如果共享状态尚未就绪,则该函数将阻塞调用的线程直到就绪3)
当共享状态就绪后,则该函数将取消阻塞并void返回
wait_for()
:等待结果,如果在指定的超时间隔后仍然无法得到结果,则返回wait_until()
:等待结果,如果在已经到达指定的时间点时仍然无法得到结果,则返回share()
:把共享状态从*this
转移到shared_future
对象,并返回shared_future
对象valid()
:检查 future 是否拥有共享状态
1.3、应用示例
假设你有一个长期运行的计算,预期最终将得到一个有用的结果,但是现在你还不需要这个值。你可以启动一个新的线程来执行该计算,这也意味着你必须注意将结果传回来,但是
std::thread
并没有提供直接的机制来这样做。实际上可以使用std::async
来启动一个异步任务。std::async
返回一个std::future
对象,而不是给你一个std::thread
对象让你在上面等待,std::thread
对象最终将持有函数的返回值。当你需要这个值时,只要在std::thread
对象上调用get()
,线程就会阻塞直到值返回,如下:
#include <future>
#include <iostream>
#include <unistd.h>
int main()
{
// future from an async()
std::future<int> f = std::async(std::launch::async, []{ sleep(5); return 8; });
std::cout << " do something else..." << std::endl;
f.wait();
std::cout << "Done!\nResults are: " << f.get() << std::endl;
}
二、std::shared_future
2.1、模版声明
std::shared_future
模版提供的功能与std::future
类似,除了允许多个线程等候同一共享状态。std::shared_future
可复制而且多个shared_future
对象能指代同一共享状态。若每个线程通过其自身的shared_future
对象副本访问,则从多个线程访问同一共享状态是安全的,模版声明如下:
template< class T > class shared_future;
2.2、应用示例
std::shared_future
可用于同时向多个线程发信,类似std::condition_variable::notify_all()
,如下:
#include <iostream>
#include <thread>
#include <future>
void promise_string(std::promise<std::string> &pr)
{
for (int i = 0; i < 50; i++)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
std::string str = "the current function name is: ";
str.append(__FUNCTION__);
pr.set_value(str);
}
int main()
{
std::promise<std::string> pr;
std::future<std::string> fu(pr.get_future());
std::shared_future<std::string> sfu(fu.share());
std::thread t1([&sfu]() {
std::string str = sfu.get();
std::cout << "thread1 function is: " << str.c_str() << std::endl;
});
std::thread t2([&sfu]() {
std::string str = sfu.get();
std::cout << "thread2 function is: " << str.c_str() << std::endl;
});
std::async(std::launch::async, [&pr]() {
promise_string(pr);
});
t1.join();
t2.join();
system("pause");
}
三、std::async
3.1、模版声明
C++11 中提供了
std::async
模版用来创建异步任务 ,并通过std::future
获取异步调用的结果。std::async
模版的第一个参数是可调用实体,同时支持传入一些参数,模版声明如下:
template< class Function, class... Args >
std::future<typename std::result_of<typename std::decay<Function>::type(
typename std::decay<Args>::type...)>::type>
async( std::launch policy, Function&& f, Args&&... args );
3.2、模版参数
policy
:启动策略,是std::launch
枚举类型f
:可调用实体args
:传入的参数
3.3、启动策略
async
函数接受两种不同的启动策略,这些策略在std::launch
枚举中定义,如下:
std::launch::defered
:这种策略意味着任务将在调用future::get()
或future::wait
函数时延迟执行,也就是任务将在需要结果时同步执行std::launch::async
:任务在单独一个线程上异步执行
默认情况下
async
使用std::launch::defered | std::launch::async
策略,这意味着任务可能异步执行,也可能延迟执行,具体取决于实现,示例:
#include <iostream>
#include <thread>
#include <future>
using namespace std;
thread::id test_async() {
this_thread::sleep_for(chrono::milliseconds(500));
return this_thread::get_id();
}
thread::id test_async_deferred() {
this_thread::sleep_for(chrono::milliseconds(500));
return this_thread::get_id();
}
int main() {
// 另起一个线程去运行test_async
future<thread::id> ans = std::async(launch::async, test_async);
// 还没有运行test_async_deferred
future<thread::id> ans_def = std::async(launch::deferred,test_async_deferred); //还没有运行test_async_deferred
cout << "main thread id = " << this_thread::get_id() << endl;
// 如果test_async这时还未运行完,程序会阻塞在这里,直到test_async运行结束返回
cout << "test_async thread id = " << ans.get() << endl;
// 这时候才去调用test_async_deferred,程序会阻塞在这里,直到test_async_deferred运行返回
cout << "test_async_deferred thread id = = " << ans_def.get() << endl;
return 0;
}
输出结果
main thread id = 1
test_async thread id = 2
test_async_deferred thread id = = 1
Process returned 0 (0x0) execution time : 1.387 s
Press any key to continue.
从输出结果可以看出采用
std::launch::defered
策略时任务是同步执行,采用launch::async
策略时任务是异步执行。
四、std::packaged_task
4.1、模版声明
std::packaged_task
模版包装了可调用实体,返回值保存到与之相关联的std::future
,包括函数运行时产生的异常。与std::promise
一样,std::packaged_task
支持move
,但不支持拷贝copy
,模版声明如下:
template< class R, class ...ArgTypes >
class packaged_task<R(ArgTypes...)>;
4.2、成员方法
valid()
:判断std::packaged_task
是否拥有可调用实体get_future()
:获取相关联的std::future
对象operator()
:执行可调用实体make_ready_at_thread_exit()
:接收的参数与operator()(_ArgTypes...)
一样,行为也一样。只有一点差别,那就是不会将计算结果立刻反馈给std::future
,而是在其执行时所在的线程结束后,std::future::get
才会取得结果reset()
:重置状态,抛弃任何先前执行的存储结果
注意:关联的
std::future
可以通过get_future()
获取到,get_future()
仅能调用一次,多次调用会触发std::future_error
异常。
4.3、std::packaged_task::valid()
通过
packaged_task::valid()
判断std::packaged_task
对象是否是有效状态。当通过缺省构造初始化时,由于其未设置任何可调用对象,valid()
返回false
。只有当std::packaged_task
设置了有效的函数或可调用对象,valid()
才返回true
。例如:
#include <future> // std::packaged_task, std::future
#include <iostream> // std::cout
int main() {
std::packaged_task<void()> task; // 缺省构造、默认构造
std::cout << std::boolalpha << task.valid() << std::endl; // false
std::packaged_task<void()> task2(std::move(task)); // 右值构造
std::cout << std::boolalpha << task.valid() << std::endl; // false
task = std::packaged_task<void()>([](){}); // 右值赋值, 可调用对象
std::cout << std::boolalpha << task.valid() << std::endl; // true
return 0;
}
4.4、std::packaged_task::operator()
调用
std::packaged_task
对象所封装可调用对象R
,但其函数原型与R
稍有不同:
void operator()(ArgTypes... );
operator()
的返回值是void
,即无返回值。因为std::packaged_task
的设计主要是用来进行异步调用,因此R(ArgTypes...)
的计算结果是通过std::future::get
来获取的。该函数会将R
的计算结果反馈给std::future
,即使R
抛出异常(此时std::future::get
也会抛出同样的异常)。
#include <future> // std::packaged_task, std::future
#include <iostream> // std::cout
int main() {
std::packaged_task<void()> convert([](){
throw std::logic_error("will catch in future");
});
std::future<void> future = convert.get_future();
convert(); // 异常不会在此处抛出
try {
future.get();
} catch(std::logic_error &e) {
std::cerr << typeid(e).name() << ": " << e.what() << std::endl;
}
return 0;
}
4.5、std::packaged_task::reset()
与
std::promise
不一样,std::promise
仅可以执行一次set_value
或set_exception
函数,但std::packagged_task
可以执行多次,其奥秘就是reset
函数
template<class _Rp, class ..._ArgTypes>
void packaged_task<_Rp(_ArgTypes...)>::reset()
{
if (!valid())
__throw_future_error(future_errc::no_state);
__p_ = promise<result_type>();
}
通过重新构造一个
promise
来达到多次调用的目的。显然调用reset
后,需要重新get_future
,以便获取下次operator()
执行的结果。由于是重新构造了promise
,因此reset
操作并不会影响之前调用的make_ready_at_thread_exit
结果,也即之前的定制的行为在线程退出时仍会发生。
五、std::promise
5.1、模版声明
std::promise
模版保存了一个值或异常,保存的值可以被关联的std::future
读取。std::promise
允许move
语义(右值构造,右值赋值),但不允许拷贝(拷贝构造、赋值),std::future
亦然。std::promise
和std::future
合作共同实现了多线程间通信,模版声明如下:
template<class _Ty>
class promise
5.2、成员方法
get_future()
:获取与std::promise
相关联的std::future
对象set_value()
:给std::promise
设置值set_value_at_thread_exit()
:std::promise
所在线程退出时,std::future
收到通过该函数设置的值set_exception_at_thread_exit
:std::promise
所在线程退出时,std::future
则抛出该函数指定的异常
注意:一个
std::promise
实例只能与一个std::future
关联共享状态,当在同一个std::promise
上反复调用get_future()
会抛出future_error
异常。
5.3、设置std::promise的值
通过成员函数
set_value
可以设置std::promise
中保存的值,该值最终会被与之关联的std::future::get
读取到。
#include <iostream>
#include <thread>
#include <string>
#include <future>
#include <chrono>
using namespace std::chrono;
void read(std::future<std::string> *future) {
// future会一直阻塞,直到有值到来
std::cout << future->get() << std::endl;
}
int main() {
// promise 相当于生产者
std::promise<std::string> promise;
// future 相当于消费者, 右值构造
std::future<std::string> future = promise.get_future();
// 另一线程中通过future来读取promise的值
std::thread thread(read, &future);
// 让当前的线程等待一段时间
std::this_thread::sleep_for(seconds(2));
// 设置值给promise
promise.set_value("hello future");
// 等待线程执行完成
thread.join();
return 0;
}
注意:
set_value
只能被调用一次,多次调用会抛出std::future_error
异常。std::promise::set_xxx
函数会改变std::promise
的状态为ready
,再次调用时发现状态已要是ready
了,则抛出异常。
5.4、std::promise不设置值
如果
std::promise
直到销毁时,都未设置过任何值,则std::promise
会在析构时自动设置为std::future_error
,这会导致std::future.get()
调用抛出std::future_error
异常,如下:
#include <iostream> // std::cout, std::endl
#include <thread> // std::thread
#include <future> // std::promise, std::future
#include <chrono> // seconds
using namespace std::chrono;
void read(std::future<int> future) {
try {
future.get();
}
catch (std::future_error &e) {
std::cerr << e.code() << "\n" << e.what() << std::endl;
}
}
int main() {
std::thread thread;
{
std::promise<int> promise;
thread = std::thread(read, promise.get_future());
}
thread.join();
return 0;
}