CPP多线程3:async和future、promise
大多数情况下使用async而不用thread。thread可以快速、方便地创建线程,但在async面前,就是小巫见大巫了。async可以根据情况选择同步执行或创建新线程来异步执行,当然也可以手动选择。对于async的返回值操作也比thread更加方便。
std::async
暂且不管它的返回值std::future是啥(后面解释),先举个例再说。
#include <iostream>
#include <future>
#include <string>
using namespace std;int main(){async(launch::async,[](const string& msg){cout<<msg;},"Hello ");cout<<"World!"<<endl;return 0;
}
你的编译器可能会给出一条警告,
warning C4834: 放弃具有 "nodiscard" 属性的函数的返回值。
这是因为编译器不想让你丢弃async的返回值std::future,不过在这个例子中不需要它,忽略这个警告就行了。
std::async参数
不同于thread,async是一个函数,所以没有成员函数。
std::launch强枚举类(enum class)
std::launch有2个枚举值和1个特殊值:
std::future
我们已经知道如何使用async来异步或同步执行任务,但如何获得函数的返回值呢?这时候,async的返回值std::future就派上用场了。例子如下:
#include <iostream>
#include <future>
using namespace std;// 可变参数模板求和函数
template <class... Args>
auto sum(Args &&...args){return (0 + ... + args); // 折叠表达式(C++17)
}int main()
{// 用lambda包装sum调用,显式传递参数,避免类型推导问题auto fut = async(launch::async, []{ return sum(1, 2, 33); // 在lambda内部内部直接调用sum,参数明确});cout << fut.get() << endl; return 0;
}
我们定义了一个函数sum,它可以计算多个数字的和,之后我们又定义了一个对象fut,它的类型是std::future,这里的int代表这个函数的返回值是int类型。在创建线程后,我们使用了future::get()来阻塞等待线程结束并获取其返回值。至于sum函数中的折叠表达式(fold expression),不是我们这篇文章的重点。
future常用成员函数
构造&析构函数
常用成员函数
为啥要有void特化的std::future?
std::future的作用并不只有获取返回值,它还可以检测线程是否已结束、阻塞等待,所以对于返回值是void的线程来说,future也同样重要。
#include <iostream>
#include <future>
using namespace std;int main(){auto fut=async(launch::async,[](){for(int i=0;i<20'0000'0000;i++);});cout<<"Please wait ";while(fut.wait_for(chrono::milliseconds(100))!=future_status::ready)cout<<".";// fut.get();// 上面也会阻塞代码,且无任何返回值cout<<endl<<"Finished!"<<endl;return 0;
}
std::promise
在上文,我们已经讲到如何获取async创建线程的返回值。不过在某些特殊情况下,我们可能需要使用thread而不是async,那么如何获得thread的返回值呢?
还记得之前我们讲的thread成员函数吗?thread::join()的返回值是void类型,所以你不能通过join来获得线程返回值。那么thread里有什么函数能获得返回值呢?
答案是:没有。
惊不惊喜?意不意外?thread竟然不能获取返回值!难道thread真的就没有办法返回点什么东西吗?如果你真是那么想的,那你就太低估C++了。一些聪明的人可能已经想到解决办法了:可以通过传递引用的方式来获取返回值。
但是单纯地使用&无法解决多线程竞争的问题,于是std提供了promise。promise实际上是std::future的一个包装,在讲解future时,我们并没有牵扯到改变future值的问题,但是如果使用thread以引用传递返回值的话,就必须要改变future的值,那么该怎么办呢?
实际上,future的值不能被改变,但你可以通过promise来创建一个拥有特定值的future。什么?没听懂?好吧,那我就举个例子:
constexpr int a = 1;
现在,把常量当成future,把a当作一个future对象,那我们想拥有一个值为2的future对象该怎么办?很简单:
constexpr int a = 1;
constexpr int b = 2;
这样,我们就不用思考如何改动a的值,直接创建一个新常量就能解决问题了。
promise的原理就是这样,不改变已有future的值,而是创建新的future对象。什么?还没听懂?好吧,记住这句话:
future的值不能改变,promise的值可以改变。
其使用示例如下:
#include <iostream>
#include <thread>
#include <future>
using namespace std;int main()
{promise<int> p;thread t([](promise<int> &p){int temp=0;for(int i=0;i<10;i++){temp+=i;}p.set_value(temp);}, ref(p));t.join();cout << p.get_future().get() << endl;return 0;
}
promise常用成员函数
构造&析构函数
常用成员函数