多线程的Future模式

348 阅读5分钟

传统的同步/异步模式

先看传统的同步模式

同步模式

再看传统的异步模式(Future模式便是对传统异步模式的封装)

异步模式

最简单的 Future 模型实现原理

参与者

角色职责
Main系统启动,调用Client发出请求
Client返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData
Data数据的接口
FutureDataFutrueData是一个虚拟的数据,构造很快,需要装配RealData
RealData真实数据,构造比较慢

UML图

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模式

直接上对比图

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

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细节:

packaged_task UML

它重点是重载了operator()运算符,在运算符里执行Callable对象,并将其返回结果set_value到 promise 中

std::async

std::promise/std::future 以及 std::packaged_task 都还没有对线程进行封装,需要我们自己去创建线程执行。故 std::async 函数的作用,就是用来将这些东西,与线程封装在一起,供用户使用,省去用户创建线程的工作量。

它的组成如下图所示: 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模式

QFuture 组成

上代码

#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信号槽。