传统的同步/异步模式
先看传统的同步模式
再看传统的异步模式(Future模式便是对传统异步模式的封装)
最简单的 Future 模型实现原理
参与者
| 角色 | 职责 |
|---|---|
| Main | 系统启动,调用Client发出请求 |
| Client | 返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData |
| Data | 数据的接口 |
| FutureData | FutrueData是一个虚拟的数据,构造很快,需要装配RealData |
| RealData | 真实数据,构造比较慢 |
UML图
代码实现
public interface Data {
String getRequest();
}
public class RealData implements Data {
private String m_requestData;
void init(String arg) {
System.out.println("正在处理参数:" + arg);
System.out.println("可能非常耗时,请耐心等待");
// ... A few minutes latter ...
m_requestData = "这里存在了处理结果";
System.out.println("处理完毕,结果已经存好了");
}
String getRequest()
{
return m_requestData;
}
}
public class FutureData implements Data {
private RealData m_real = null;
void setRealData(RealData realData) {
m_real = realData;
notify(); // 唤醒等待的线程,有数据了
}
String getRequest()
{
if (null == m_read) {
wait();
}
return m_real.getRequest();
}
}
public class Client {
Data request(String arg) {
FutureData future = new FutureData();
// 开启后台线程去工作
new Thread(new Runnable() {
public void run() {
RealData real = new RealData();
real.init(arg);
future.setRealData(real);
}
}).start();
return future; // 立即返回一个FutureData供外部使用
}
}
public static void main(String args[]) {
Client client = new Client;
Data data = client.request("传点参数");
System.out.println("请求已经发出,本线程现在可以干点别的活了");
// ... 这里可以先做做别的工作
System.out.println("活都干完了,去取请求的结果吧");
String result = data .getRequest();
System.out.println("拿到结果了,结果为:" + result);
}
C++ 的Future模式
直接上对比图
std::future 和 std::promise
它俩是成对出现的,像是一对小情侣。
- 男方许下一个承诺(promise):一年以后咱们能买辆新车
- 女方拿着他画的这块饼(future),期盼着一年后的结果
- 这一年期间,男的一直在为买车忙碌(后台线程)
- 这一年期间,女的该干嘛干嘛,啥事也没耽误。一年以后,她再去看结果也不迟(前台线程)
直接上代码,看下它俩是怎么玩的。
void boyfriend(std::promise<bool>& buyACar)
{
// 男方带着承诺过了一年,这一年他在这里执行后台计算,各种耗时操作
// ... One year latter
buyACar.set_value(true); // 成功买下一辆新车
}
void girlfriend(std::future<bool>& daBing)
{
// 女方处理其它事务,不着急去要结果,等闲时再去看看有没有结果
// ... One year latter
// 方式1:可以通过超时查询的方式,去看看男方大饼准备好没
if (std::future_status::ready == daBing.wait_for(std::chrono::seconds(1)))
{
bool isDaBingSucceed = daBing.get(); // 取结果
}
// 方式2:可以啥事也不干了,进入直接等待状态,直到男方大饼画完,函数再返回
bool isDaBingSucceed = daBing.get(); // 取结果
if (isDaBingSucceed)
{
std::cout << "我靠你真的做到了,好牛逼啊你!";
}
else
{
std::cout << "你画的大饼呢?我没吃到!";
}
}
void main()
{
// 起草一份承诺书,一式两份(一份交由男方去执行,一份交由女方到期后去领取)
std::promise<bool> buyACar;
auto daBing = buyACar.get_future();
// 给他俩一个一个线程,各玩各的
std::thread t1(boyfriend, std::ref(buyACar));
std::thread t2(girlfriend, std::ref(daBing));
t1.join();
t2.join();
}
透过上面的代码可以看出 std::future 不是独立存在的,它和 std::promise 是绑定在一起的,由 std::promise 的get_future接口获取
std::packaged_task
透过前面 std::future 和 std::promise 示例,可以看出,它俩的功能就是:
- std::promise 提供数据的
set接口,用于后台线程将执行结果存储到其中 - std::future 提供数据的
get接口,用于读取后台线程的执行结果
此时真正干活的函数,需要我们自己写代码,将它与 std::promise/std::future 关联起来。 那这一过程,是不是可以再封装一层呢?答案是可以,这就是 std::packaged_task
它封装了2部分内容:
- Callable对象:可调用的对象,可以是函数、Lambda表达式、std::function等一切可被调用的东西
- Promise:std::promise/std::future 一式两份的大饼承诺书
上代码:
int Add(int a, int b)
{
return a + b;
}
void test1()
{
std::packaged_task<int(int, int)> task(Add);
task(1, 2); // 接入调用它的opeartor()运算符。执行进Add函数
std::future<int> daBing = task.get_future();
int sum = daBing.get(); // sum = 1 + 2 = 3
}
void test2()
{
// 包一个lambda表达式
std::packaged_task<int(int, int)> task([](int a, int b) { return a + b; });
std::future<int> daBing = task.get_future();
// 交给后台线程去跑
std::thread td(move(task), 2, 10);
td.join();
int sum = daBing.get(); // sum = 1 + 2 = 3
}
到这里,再结合上前面它的UML图,看下packaged_task细节:
它重点是重载了operator()运算符,在运算符里执行Callable对象,并将其返回结果set_value到 promise 中
std::async
std::promise/std::future 以及 std::packaged_task 都还没有对线程进行封装,需要我们自己去创建线程执行。故 std::async 函数的作用,就是用来将这些东西,与线程封装在一起,供用户使用,省去用户创建线程的工作量。
它的组成如下图所示:
当然,std::async 也对一些调用场景做了优化,比如将你的后台处理代码,直接扔到前面线程中执行, 或者延迟执行代码等等。多说无益,直接上代码:
std::string fetchDataFromDB(std::string recvData) {
std::cout << "fetchDataFromDB start" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(seconds(5));
return "DB_" + recvData;
}
std::string fetchDataFromFile(std::string recvData) {
std::cout << "fetchDataFromFile start" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(seconds(3));
return "File_" + recvData;
}
int main() {
std::packaged_task<int(int)> pack;
std::cout << "main start" << std::this_thread::get_id() << std::endl;
//获取开始时间
system_clock::time_point start = system_clock::now();
std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
//从文件获取数据
std::future<std::string> fileData = std::async(std::launch::deferred, fetchDataFromFile, "Data");
//知道调用get函数fetchDataFromFile才开始执行
std::string FileData = fileData.get();
//如果fetchDataFromDB()执行没有完成,get会一直阻塞当前线程
std::string dbData = resultFromDB.get();
//获取结束时间
auto end = system_clock::now();
auto diff = duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
//组装数据
std::string data = dbData + " :: " + FileData;
//输出组装的数据
std::cout << "Data = " << data << std::endl;
return 0;
}
程序输出结果
main start12884
fetchDataFromFile start12884
fetchDataFromDB start3788
Total Time taken= 5Seconds
Data = DB_Data :: File_Data
Qt 中的Future模式
上代码
#include <QCoreApplication>
#include <QtConcurrent/QtConcurrentRun>
#include <QDebug>
void hello(const QString &name)
{
qDebug() << "Hello" << name << "from" << QThread::currentThread();
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "Main Thread" << QThread::currentThread();
// 在一个单独的线程中调用 hello()
QFuture<void> f1 = QtConcurrent::run(hello, QString("Qter"));
QFuture<void> f2 = QtConcurrent::run(hello, QString("Pythoner"));
// 阻塞调用线程并等待计算完成,确保所有结果可用
f1.waitForFinished();
f2.waitForFinished();
}
程序输出结果:
Main Thread QThread(0x398fc0)
Hello “Qter” from QThread(0x39c240, name = “Thread (pooled)”)
Hello “Pythoner” from QThread(0x39c280, name = “Thread (pooled)”)
再看看 QFutureWatcher 的简单示例:
MyClass myObject;
QFutureWatcher<int> watcher;
connect(&watcher, SIGNAL(finished()), &myObject, SLOT(handleFinished()));
// 开始计算
QFuture<int> future = QtConcurrent::run(...);
watcher.setFuture(future);
QFutureWatcher 就是在 QFuture 基础上,封装了一层Qt信号槽。