c++11多线程编程同步——使用future和promise

简介

在多线程编程中,需要数据共享,如一个线程需要使用另一线程运算产生的数据。

涉及到异步编程时,有时需要线程间同步,如执行异步订阅消息时,当订阅消息的结果返回成功后,处理消息的线程才需要运行。

以上场景的都可以使用std::future和std::promise实现。

std::promise是可以存储类型T的值的对象,该值可以被另一线程的std::future对象获取,并提供了同步机制。
std::future是一个对象,可以从某个对象或函数获取值,并在不同线程之间提供恰当的同步访问。

简单地说,promise存储了一个在未来会改变其值(类型为T)的对象,并且在未来会改变该值,而future则存储一个已知其值在将来会改变的值,并且在该值改变后可以线程安全地读取。

future一般和promise配合使用。

使用

  1. 多线程共享值

按以下步骤:

  • 在一个线程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获取到的,自己构造出来的无效
  1. 线程间同步

其实,也可以使用如下代码判断共享对象的值是否就绪:

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

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值