配套视频课程已更新完毕,大家可通过以下两种方式观看视频讲解:

关注公众号:爱编程的大丙,或者进入大丙课堂学习。


1. std::future

C++11中增加的线程类,使得我们能够非常方便的创建和使用线程,但有时会有些不方便,比如需要获取线程返回的结果,就不能通过join()得到结果,只能通过一些额外手段获得,比如:定义一个全局变量,在子线程中赋值,在主线程中读这个变量的值,整个过程比较繁琐。C++提供的线程库中提供了一些类用于访问异步操作的结果。

那么,什么叫做异步呢?

我们去星巴克买咖啡,因为都是现磨的,所以需要等待,但是我们付完账后不会站在柜台前死等,而是去找个座位坐下来玩玩手机打发一下时间,当店员把咖啡磨好之后,就会通知我们过去取,这就叫做异步。

  • 顾客(主线程)发起一个任务(子线程磨咖啡),磨咖啡的过程中顾客去做别的事情了,有两条时间线(异步)
  • 顾客(主线程)发起一个任务(子线程磨咖啡),磨咖啡的过程中顾客没去做别的事情而是死等,这时就只有一条时间线(同步),此时效率相对较低。

因此多线程程序中的任务大都是异步的,主线程和子线程分别执行不同的任务,如果想要在主线中得到某个子线程任务函数返回的结果可以使用C++11提供的std:future类,这个类需要和其他类或函数搭配使用,先来介绍一下这个类的API函数:

类的定义

通过类的定义可以得知,future是一个模板类,也就是这个类可以存储任意指定类型的数据。

1
2
3
4
// 定义于头文件 <future>
template< class T > class future;
template< class T > class future<T&>;
template<> class future<void>;

构造函数

1
2
3
4
5
6
// ①
future() noexcept;
// ②
future( future&& other ) noexcept;
// ③
future( const future& other ) = delete;
  • 构造函数①:默认无参构造函数
  • 构造函数②:移动构造函数,转移资源的所有权
  • 构造函数③:使用=delete显示删除拷贝构造函数, 不允许进行对象之间的拷贝

常用成员函数(public)

一般情况下使用=进行赋值操作就进行对象的拷贝,但是future对象不可用复制,因此会根据实际情况进行处理:

  • 如果other是右值,那么转移资源的所有权
  • 如果other是非右值,不允许进行对象之间的拷贝(该函数被显示删除禁止使用
1
2
future& operator=( future&& other ) noexcept;
future& operator=( const future& other ) = delete;

取出future对象内部保存的数据,其中void get()是为future<void>准备的,此时对象内部类型就是void,该函数是一个阻塞函数,当子线程的数据就绪后解除阻塞就能得到传出的数值了。

1
2
3
T get();
T& get();
void get();

因为future对象内部存储的是异步线程任务执行完毕后的结果,是在调用之后的将来得到的,因此可以通过调用wait()方法,阻塞当前线程,等待这个子线程的任务执行完毕,任务执行完毕当前线程的阻塞也就解除了。

1
void wait() const;

如果当前线程wait()方法就会死等,直到子线程任务执行完毕将返回值写入到future对象中,调用wait_for()只会让线程阻塞一定的时长,但是这样并不能保证对应的那个子线程中的任务已经执行完毕了。

wait_until()wait_for()函数功能是差不多,前者是阻塞到某一指定的时间点,后者是阻塞一定的时长。

1
2
3
4
5
template< class Rep, class Period >
std::future_status wait_for( const std::chrono::duration<Rep,Period>& timeout_duration ) const;

template< class Clock, class Duration >
std::future_status wait_until( const std::chrono::time_point<Clock,Duration>& timeout_time ) const;

wait_until()wait_for()函数返回之后,并不能确定子线程当前的状态,因此我们需要判断函数的返回值,这样就能知道子线程当前的状态了:

常量 解释
future_status::deferred 子线程中的任务函仍未启动
future_status::ready 子线程中的任务已经执行完毕,结果已就绪
future_status::timeout 子线程中的任务正在执行中,指定等待时长已用完

2. std::promise

std::promise是一个协助线程赋值的类,它能够将数据和future对象绑定起来,为获取线程函数中的某个值提供便利。

2.1 类成员函数

类定义

通过std::promise类的定义可以得知,这也是一个模板类,我们要在线程中传递什么类型的数据,模板参数就指定为什么类型。

1
2
3
4
// 定义于头文件 <future>
template< class R > class promise;
template< class R > class promise<R&>;
template<> class promise<void>;

构造函数

1
2
3
4
5
6
// ①
promise();
// ②
promise( promise&& other ) noexcept;
// ③
promise( const promise& other ) = delete;
  • 构造函数①:默认构造函数,得到一个空对象
  • 构造函数②:移动构造函数
  • 构造函数③:使用=delete显示删除拷贝构造函数, 不允许进行对象之间的拷贝

公共成员函数

std::promise类内部管理着一个future类对象,调用get_future()就可以得到这个future对象了

1
std::future<T> get_future();

存储要传出的 value 值,并立即让状态就绪,这样数据被传出其它线程就可以得到这个数据了。重载的第四个函数是为promise<void>类型的对象准备的。

1
2
3
4
void set_value( const R& value );
void set_value( R&& value );
void set_value( R& value );
void set_value();

存储要传出的 value 值,但是不立即令状态就绪。在当前线程退出时,子线程资源被销毁,再令状态就绪。

1
2
3
4
void set_value_at_thread_exit( const R& value );
void set_value_at_thread_exit( R&& value );
void set_value_at_thread_exit( R& value );
void set_value_at_thread_exit();

2.2 promise的使用

通过promise传递数据的过程一共分为5步:

  1. 在主线程中创建std::promise对象
  2. 将这个std::promise对象通过引用的方式传递给子线程的任务函数
  3. 在子线程任务函数中给std::promise对象赋值
  4. 在主线程中通过std::promise对象取出绑定的future实例对象
  5. 通过得到的future对象取出子线程任务函数中返回的值。

子线程任务函数执行期间,让状态就绪

1
2