简介
在多线程编程中,需要数据共享,如一个线程需要使用另一线程运算产生的数据。
涉及到异步编程时,有时需要线程间同步,如执行异步订阅消息时,当订阅消息的结果返回成功后,处理消息的线程才需要运行。
以上场景的都可以使用std::future和std::promise实现。
std::promise是可以存储类型T的值的对象,该值可以被另一线程的std::future对象获取,并提供了同步机制。
std::future是一个对象,可以从某个对象或函数获取值,并在不同线程之间提供恰当的同步访问。
简单地说,promise存储了一个在未来会改变其值(类型为T)的对象,并且在未来会改变该值,而future则存储一个已知其值在将来会改变的值,并且在该值改变后可以线程安全地读取。
future一般和promise配合使用。
使用
- 多线程共享值
按以下步骤:
- 在一个线程A中创建一个 std::promise 对象:
std::promise<int> proObj;
- 把 std::promise 关联到 std::future:
std::future<int> futObj = proObj.get_future();
- 把 proObj 对象传递给另一个线程B,线程B会在适当的时刻设置该对象的值:
proObj.set_value(23);
- 在线程A中通过 std::future 读取对象的值:
int val = futObj.get();
就是这么简单,不管线程B在何时设置对象的值,线程A总能安全地获取。有一个点需要注意,在A中通过futObj.get()
获取值时,若B还未设置,则A会阻塞,直到设置了值并成功获取。
完整的流程如下图所示(来自参考资料):
代码示例如下:
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
// 线程B
void initiazer(std::promise<int> * promObj)
{
std::cout << "Thread B" << std::endl;
// set the value at proper time
std::this_thread::sleep_for(std::chrono::seconds(3));
promObj->set_value(23);
}
int main()
{
// 线程A
std::promise<int> promiseObj;
std::future<int> futureObj = promiseObj.get_future();
std::thread th(initiazer, &promiseObj); // 启动线程B
// 获取对象的值,该调用在B设置其值后会返回23,在B设置其值前会阻塞
std::cout<< futureObj.get() << std::endl;
th.join();
return 0;
}
如上代码中,一旦promObj调用set_value设置了对象的值,该对象的共享状态就变更为ready,futureObj就能使用get()函数获取到值。
注意:
- 只能从promise共享状态获取一个future对象,不能把两个future关联到同一个promise
- 如果promise不设置值或者异常,promise 对象在析构时会自动地设置一个 future_error 异常(broken_promise)来设置其自身的就绪状态
- promise 对象的set_value只能被调用一次,多次调用会抛出std::future_error异常(因为第一次调用后状态变更为ready)
- std::future是通过std::promise::get_future获取到的,自己构造出来的无效
- 线程间同步
其实,也可以使用如下代码判断共享对象的值是否就绪:
template<typename T>
bool is_ready(std::future<T> const& f)
{ return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; }
future对象的wait_for函数会阻塞等待结果变得可用,可用的标志为以下两种情况之一:
- 设置的时间超时
- 共享对象的状态变为ready
它的原型如下:
template< class Rep, class Period >
std::future_status wait_for( const std::chrono::duration<Rep,Period>& timeout_duration ) const;
返回值标识了结果的状态,为:
- future_status::deferred :计算结果的函数未启动
- future_status::ready:结果ready
- future_status::timeout:超时
利用超时特性,线程同步的方式如下(从上例代码修改):
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
// 线程B
void loginSrv(std::promise<int> * promObj)
{
std::cout << "Thread B" << std::endl;
// set the value
std::this_thread::sleep_for(std::chrono::seconds(3));
if (loginSucc)
promObj->set_value(true);
else
promObj->set_value(23);
}
int main()
{
// 线程A
std::promise<bool> promiseObj;
std::future<bool> futureObj = promiseObj.get_future();
std::thread th(loginSrv, &promiseObj); // 启动线程B
// 阻塞10s等待登录结果,若10s内未返回结果,则超时
if (futInit.wait_for(std::chrono::seconds(10)) == std::future_status::ready)
if (futInit.get()) // 返回登录结果,不会再阻塞
doLoginSuccThings;
else
doLoginFailedThings;
else
// 超时未登录成功
doOtherThings;
th.join();
return 0;
}
注意:由于线程调度或者资源竞态等原因,wait_for函数阻塞的时间可能会超过指定的time_out时长。
总结
通过以上两个简单的示例,可以看出,使用promise和future能够快速地进行线程间数据共享和同步。
promise提供值,并在以后改变值(set_value)。
future关联到promise,线程安全地获取值(get)。
利用future的阻塞等待特性(wait_for),可以实现线程同步。
c11中提供了大量用于多线程编程的实用技术,promise和future只是冰山一角,更多内容等待探索。
参考资料
C++11 Multithreading – Part 8: std::future , std::promise and Returning values from Thread
What is std::promise
Why does std::future::wait_for not wait for the correct duration