多线程07:async、future、packaged_task、promise

📕async、future、packaged_task、promise

本节内容需要包含头文件: #include <future></future>

一、std::async、 std::future 创建后台任务并返回值

①:启用async

  • std::async是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后, 它返回一个std::future对象,这个对象是个类模板。
  • “启动一个异步任务”:就是自动创建一个线程,并开始 执行对应的线程入口函数,它返回一个std::future对象,这个std::future对象中就含有线程入口函数所返回的结果, 我们可以通过调用future对象的成员函数get()来获取结果。
  • “future”将来的意思,也有人称呼 std::future提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,但是在不久的将来,这个线程执行完毕的时候,你就能够拿到结果了,所以,大家这么理解: future中保存着一个值,这个值是在将来的某个时刻能够拿到
  • std::future对象的 get()成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像join()。但是,它是可以获取结果的。 并且记得一个future只能调用一次get(无论哪里,都会将数据转移),调用两次程序会崩溃。
  • std::future对象的 wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和 std::thread 的join()更像。没有只能调用一次限制,但是调用多也没有效果。
  • 如果不调用future的get()成员函数和wait()成员函数,程序也会在主函数return处一直等待调用子线程结束;

看例子:

#include
#include
using namespace std;
class A {
public:
    int mythread(int data) {
        cout << data << endl;
        return data * 2;
    }
};

int mythread() {
    cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000);//睡5秒
    std::this_thread::sleep_for(dura);
    cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
    return 5;
}

int main() {
    A a;
    int tmp = 12;
    cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
    std::future result1 = std::async(mythread);
    cout << "continue........" << endl;
    cout << result1.get() << endl; //卡在这里等待mythread()执行完毕,拿到结果

    //类成员函数
    std::future result2 = std::async(&A::mythread, &a, tmp); //第二个参数是对象引用才能保证线程里执行的是同一个对象
    //cout << result2.get() << endl;//get()只能调用一次,result2不再有结果值,转移的是存的数据类型,这里是int!
   //或者result2.wait();
    cout << "finish all" << endl;
    return 0;
}

②:std::async的第一个参数

  • 参数:std::launch::deferred: 表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行; *那如果wait()或者get()没调用,那么线程没执行!线程没有创建!是在主线程中调用的线程入口函数!
#include
#include
using namespace std;

int mythread() {
    cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000);
    std::this_thread::sleep_for(dura);
    cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
    return 5;
}

int main() {
    cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
    std::future result1 = std::async(std::launch::deferred ,mythread);
    cout << "continue........" << endl;
    cout << result1.get() << endl; //卡在这里等待mythread()执行完毕,拿到结果
    cout << "finish all" << endl;
    return 0;
}

多线程07:async、future、packaged_task、promise
  • 参数:std::launch::async:在调用async函数的时候就开始创建线程;会强制std::async创建新线程,和thread一样。
  • 带上两个参数:std::launch::async |std::launch::deferred 这里这个 |:以为这调用async的行为可能是 创建新线程并立即执行,或者没有创建新线程并且延迟调用result.get()才开始执行任务入口函数,两者居其一。(系统会自行决定是异步(创建新线程)还是同步(不创建新线程)方式运行),默认值就是这个!

二、std::package_task

  • 目的就是:打包任务,把任务包装起来
  • std::packaged_task是个模板类,它的模板参数是各种可调用对象(函数,函数指针,lambda表达式,bind创建的对象,以及重载了函数调用符的类,有点像function);通过std::packaged_task来把各种可调用对象包装起来,方便将来作为线程入口函数来调用。
  • packaged_task包装起来的可调用对象还可以直接调用,所以从这个角度来讲,packaged_task对象,也是一个可调用对象。
  • 看例子:
//可调用对象是普通函数
#include
#include
#include
using namespace std;

int mythread(int mypar) {
    cout << mypar << endl;
    cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000);
    std::this_thread::sleep_for(dura);
    cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
    return 5;
}

int main() {
    cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
    //我们把函数mythread通过packaged_task包装起来
    //参数是一个int,返回值类型是int
    std::packaged_task mypt(mythread);
    std::thread t1(std::ref(mypt), 1);//传入真引用!不可以用detach模式
    t1.join();
    std::future result = mypt.get_future();
    //std::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果。
    cout << result.get() << endl;

    return 0;
}

//可调用对象是lambda表达式
int main() {
    cout << "main" << "threadid = " << std::this_thread::get_id() << endl;

    std::packaged_task mypt([](int mypar) {
        cout << mypar << endl;
        cout << "mythread() start" << " threadid = " << std::this_thread::get_id() << endl;
        std::chrono::milliseconds dura(5000);
        std::this_thread::sleep_for(dura);
        cout << "mythread() end" << " threadid = " << std::this_thread::get_id() << endl;
        return 5;
    });

    std::thread t1(std::ref(mypt), 1);//传真引用,因为是packege_task,mypt只能再被调用一次
    t1.join();
    std::future result = mypt.get_future();

    cout << result.get() << endl;

    cout << "finish overhead" << endl;
    return 0;
}

多线程07:async、future、packaged_task、promise
//调用对象是lambda表达式,直接调用
int main() {
    cout << "main" << "threadid = " << std::this_thread::get_id() << endl;

    std::packaged_task mypt([](int mypar) {
        cout << mypar << endl;
        cout << "mythread() start" << " threadid = " << std::this_thread::get_id() << endl;
        std::chrono::milliseconds dura(5000);
        std::this_thread::sleep_for(dura);
        cout << "mythread() end" << " threadid = " << std::this_thread::get_id() << endl;
        return 5;
    });

    mypt(1);//也可以在主线程中直接调用,这样就和function的效果一样,但是只能调用一次
    std::future result = mypt.get_future();
    cout << result.get() << endl;

    cout << "finish overhead" << endl;
    return 0;
}

多线程07:async、future、packaged_task、promise

tip:了解ref

三、promise

  • std::promise 类模板, 我们能够在某个线程中给它赋值,然后我们可以在其他线程中把这个值取出来用;
  • 总结: *通过promise保存一个值,在将来某时刻我们通过把一个future绑定到这个promise上来得到这个绑定的值;
#include
#include
#include
#include

using namespace std;

void mythread(std::promise&tmpp, int calc)
{
    //做一系列复杂的操作
    calc++;
    calc *= 10;

    //做其他运算,比如整整花费了5秒钟
    std::chrono::milliseconds dura(5000); //定一个5秒的时间
    std::this_thread::sleep_for(dura);  //休息一定时常

    int result = calc; //保存结果
    tmpp.set_value(result);  //####1.结果保存到了tmpp这个promise对象中
}

void mythread2(std::future &tmpf)
{
    auto result = tmpf.get();
    cout <

再了解async\future\等

四、std::asyn深入理解

4.1 std::async参数详述

  • 延迟调用参数 std::launch::deferred【延迟调用】,std::launch::async【强制创建一个线程】;
  • std::async()我们一般不叫创建线程(他能够创建线程),我们一般叫它创建一个异步任务。std::async和std::thread最明显的不同: 就是 async 有时候并不创建新线程

①如果用std::launch::deferred 来调用async?

延迟到调用 get() 或者 wait() 时执行,如果不调用就不会执行

②如果用std::launch::async来调用async?

强制这个异步任务在新线程上执行,这意味着,系统必须要创建出新线程来运行入口函数。

③如果同时用 std::launch::async | std::launch::deferred

这里这个 | 意味着async的行为可能是 std::launch::async 创建新线程立即执行, 也可能是 std::launch::deferred 没有创建新线程并且延迟到调用get()执行,由系统根据实际情况来决定采取哪种方案

④不带额外参数 std::async(mythread),只给async 一个入口函数名,此时的系统给的默认值是 std::launch::async | std::launch::deferred 和 ③ 一样,有系统自行决定异步还是同步运行。

4.2 std::async和std::thread()区别

  • std::thread()如果系统资源紧张可能出现创建线程失败的情况,如果创建线程失败那么程序就可能崩溃,而且不容易拿到函数返回值(不是拿不到)
  • *std::async()创建异步任务。可能创建线程也可能不创建线程,并且容易拿到线程入口函数的返回值;

由于系统资源限制:
①如果用std::thread创建的线程太多,则可能创建失败,系统报告异常,崩溃。

②如果用std::async,一般就不会报异常,因为如果系统资源紧张,无法创建新线程的时候,async不加额外参数的调用方式就不会创建新线程。而是在后续调用get()请求结果时执行在这个调用get()的线程上。

如果你强制async一定要创建新线程就要使用 std::launch::async 标记。承受的代价是,系统资源紧张时可能崩溃。

③根据经验, 一个程序中线程数量 不宜超过100~200

4.3 async不确定性问题的解决

让系统自行决定是否创建线程: std::future<int> result = std::async(mythread);</int>问题焦点在于这个写法,任务到底有没有被推迟执行!

如何判断: 通过future中wait_for()方法的返回值;详情看下一节内容

多阅读高手代码,多积累,提升自己技术!

Original: https://www.cnblogs.com/D-booker/p/16272045.html
Author: D-booker
Title: 多线程07:async、future、packaged_task、promise

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/604845/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球