C++并发与多线程(四)async、future、packaged_task、promise、shared_future(上)

简介: C++并发与多线程(四)async、future、packaged_task、promise、shared_future(上)

本文系列大部分来自c++11并发与多线程视频课程的学习笔记,系列文章有(不定期更新维护):


  • C++并发与多线程(一)线程传参
  • C++并发与多线程(二) 创建多个线程、数据共享问题分析、案例代码
  • C++并发与多线程(三)单例设计模式与共享数据分析、call_once、condition_variable使用
  • C++并发与多线程(四)async、future、packaged_task、promise、shared_future
  • C++并发与多线程(五)互斥量,atomic、与线程池

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


  之前,我们用std::thread创建一个线程,用join()等待这个线程结束,如果希望线程返回一个结果呢?

  std::async是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个std::future对象,这个对象也是个类模板。什么叫“启动一个异步任务”?就是自动创建一个线程,并开始 执行对应的线程入口函数,它返回一个std::future对象,这个std::future对象中就含有线程入口函数所返回的结果,我们可以通过调用future对象的成员函数get()来获取结果。

  “future”将来的意思,也有人称呼std::future提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,但是在不久的将来,这个线程执行完毕的时候,你就能够拿到结果了,所以,大家这么理解:future中保存着一个值,这个值是在将来的某个时刻能够拿到。

#include <iostream>
#include <future>
using namespace std;
int mythread(){
    cout << "mythread() started and the thread id is " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000); // 休息五秒
    std::this_thread::sleep_for(dura);
    cout << "mythread() ended and the thread id is " << std::this_thread::get_id() << endl;
    return 5;
}
int main(){
    cout << "main started and the thread id is " << std::this_thread::get_id() << endl;
    std::future<int> res = std::async(mythread); // 创建一个线程并开始执行
    cout << "continue ....." << endl;
    cout << "res.get() is : " << res.get() << endl; // 执行到get的时候,会卡在此行,等待mythread执行完毕。
    cout << "main ended and the thread id is " << std::this_thread::get_id() << endl;
}

  程序输出结果为:

main started and the thread id is 0x1000e3d40
continue .....
res.get() is : mythread() started and the thread id is 0x16fe87000
mythread() ended and the thread id is 0x16fe87000
5
main ended and the thread id is 0x1000e3d40

  std::future对象的get()成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像join()但是,它是可以获取结果的。而std::future对象的wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和std::threadjoin()更像。

#include <iostream>
#include <future>
using namespace std;
int mythread(){
    cout << "mythread() started and the thread id is " << std::this_thread::get_id() << endl;
    std::chrono::milliseconds dura(5000); // 休息五秒
    std::this_thread::sleep_for(dura);
    cout << "mythread() ended and the thread id is " << std::this_thread::get_id() << endl;
    return 5;
}
int main(){
    cout << "main started and the thread id is " << std::this_thread::get_id() << endl;
    std::future<int> res = std::async(mythread); // 创建一个线程并开始执行
    cout << "continue ....." << endl;
    //cout << "res.get() is : " << res.get() << endl; // 执行到get的时候,会卡在此行,等待mythread执行完毕。
    res.wait(); // 等待线程返回,但是拿不到返回值,类似join。
    cout << "main ended and the thread id is " << std::this_thread::get_id() << endl;
}

  同样的,我们可以使用类成员函数作为线程的入口函数:

#include <iostream>
#include <future>
using namespace std;
class A{
public:
    int mythread(int num){
        cout << "mythread() started and the thread id is " << std::this_thread::get_id() << endl;
        cout << "num is: " << num << endl;
        std::chrono::milliseconds dura(5000); // 休息五秒
        std::this_thread::sleep_for(dura);
        cout << "mythread() ended and the thread id is " << std::this_thread::get_id() << endl;
        return 5;
    }
};
int main(){
    A a;
    cout << "main started and the thread id is " << std::this_thread::get_id() << endl;
    std::future<int> res = std::async(&A::mythread, &a, 10); // 第二个参数是对象引用,如果不用引用的话,就会创建一个新的类A的对象。
    cout << "continue ....." << endl;
    cout << "res.get() is : " << res.get() << endl; // 执行到get的时候,会卡在此行,等待mythread执行完毕。
    cout << "main ended and the thread id is " << std::this_thread::get_id() << endl;
}

  我们可以通过向std::async()额外传递一个参数,该参数是std::launch类型(枚举类型),来达到一些特殊的目的:

  1. std::lunch::deferred:(defer推迟,延期)表示线程入口函数的调用会被延迟,一直到std::futurewait()或者get()函数被调用时(由主线程调用)才会执行;如果wait()或者get()没有被调用,则不会执行。
#include <iostream>
#include <future>
using namespace std;
class A{
public:
    int mythread(int num){
        cout << "mythread() started and the thread id is " << std::this_thread::get_id() << endl;
        cout << "num is: " << num << endl;
        std::chrono::milliseconds dura(5000); // 休息五秒
        std::this_thread::sleep_for(dura);
        cout << "mythread() ended and the thread id is " << std::this_thread::get_id() << endl;
        return 5;
    }
};
int main(){
    A a;
    cout << "main started and the thread id is " << std::this_thread::get_id() << endl;
    std::future<int> res = std::async(std::launch::deferred,&A::mythread, &a, 10); // 第二个参数是对象引用,如果不用引用的话,就会创建一个新的类A的对象。
    cout << "continue ....." << endl;
    cout << "res.get() is : " << res.get() << endl; // 执行到get的时候,会卡在此行,等待mythread执行完毕。
    cout << "main ended and the thread id is " << std::this_thread::get_id() << endl;
}

  程序输出结果为:

main started and the thread id is 0x1000e7d40
continue .....
res.get() is : mythread() started and the thread id is 0x1000e7d40
num is: 10
mythread() ended and the thread id is 0x1000e7d40
5
main ended and the thread id is 0x1000e7d40
Program ended with exit code: 0

  可以看到主线程id0x1000e7d40,子线程id同样为0x1000e7d40,也就是说,实际上根本就没有创建新线程。std::lunch::deferred意思时延迟调用,并没有创建新线程,是在主线程中调用的线程入口函数。上述代码永远都会先打印出continue…,然后才会打印出mythread() startmythread() end等信息。

  1. std::launch::async,在调用async函数的时候就开始创建新线程。
#include <iostream>
#include <future>
using namespace std;
class A{
public:
    int mythread(int num){
        cout << "mythread() started and the thread id is " << std::this_thread::get_id() << endl;
        cout << "num is: " << num << endl;
        std::chrono::milliseconds dura(5000); // 休息五秒
        std::this_thread::sleep_for(dura);
        cout << "mythread() ended and the thread id is " << std::this_thread::get_id() << endl;
        return 5;
    }
};
int main(){
    A a;
    cout << "main started and the thread id is " << std::this_thread::get_id() << endl;
    std::future<int> res = std::async(std::launch::async,&A::mythread, &a, 10); // 第二个参数是对象引用,如果不用引用的话,就会创建一个新的类A的对象。
    cout << "continue ....." << endl;
    cout << "res.get() is : " << res.get() << endl; // 执行到get的时候,会卡在此行,等待mythread执行完毕。
    cout << "main ended and the thread id is " << std::this_thread::get_id() << endl;
}

  程序输出结果为:

main started and the thread id is 0x1000e7d40
continue .....
res.get() is : mythread() started and the thread id is 0x16fe87000
num is: 10
mythread() ended and the thread id is 0x16fe87000
5
main ended and the thread id is 0x1000e7d40
Program ended with exit code: 0

std::packaged_task

  std::packaged_task打包任务,把任务包装起来。也是一个类模板,它的模板参数是各种可调用对象,通过packaged_task把各种可调用对象包装起来,方便将来作为线程入口函数来调用。

#include <iostream>
#include <future>
using namespace std;
int mythread(int num){
    cout << "mythread() started and the thread id is " << std::this_thread::get_id() << endl;
    cout << "num is: " << num << endl;
    std::chrono::milliseconds dura(5000); // 休息五秒
    std::this_thread::sleep_for(dura);
    cout << "mythread() ended and the thread id is " << std::this_thread::get_id() << endl;
    return 5;
}
int main(){
    cout << "main started and the thread id is " << std::this_thread::get_id() << endl;
    //我们把函数mythread通过packaged_task包装起来。参数是一个int,返回值类型是int
    std::packaged_task<int(int)> mypt(mythread);
    std::thread mythread(std::ref(mypt), 10); // 线程直接开始执行,第二个参数为线程入口参数
    mythread.join();
    //std::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果。
    std::future<int> res = mypt.get_future();
    cout << "res.get() is : " << res.get() << endl;
    cout << "main ended and the thread id is " << std::this_thread::get_id() << endl;
}

  输出结果为:

main started and the thread id is 0x1000e3d40
mythread() started and the thread id is 0x16fe87000
num is: 10
mythread() ended and the thread id is 0x16fe87000
res.get() is : 5
main ended and the thread id is 0x1000e3d40
Program ended with exit code: 0

  可调用对象可由函数换成lambda表达式:

#include <iostream>
#include <future>
using namespace std;
int main(){
    cout << "main started and the thread id is " << std::this_thread::get_id() << endl;
    //我们把函数mythread通过packaged_task包装起来。参数是一个int,返回值类型是int
    std::packaged_task<int(int)> mypt([](int num){
        cout << "mythread() started and the thread id is " << std::this_thread::get_id() << endl;
        cout << "num is: " << num << endl;
        std::chrono::milliseconds dura(5000); // 休息五秒
        std::this_thread::sleep_for(dura);
        cout << "mythread() ended and the thread id is " << std::this_thread::get_id() << endl;
        return 5;
    });
    std::thread mythread(std::ref(mypt), 10); // 线程直接开始执行,第二个参数为线程入口参数
    mythread.join();
    //std::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果。
    std::future<int> res = mypt.get_future();
    cout << "res.get() is : " << res.get() << endl;
    cout << "main ended and the thread id is " << std::this_thread::get_id() << endl;
}

  输出结果为:

main started and the thread id is 0x1000e3d40
mythread() started and the thread id is 0x16fe87000
num is: 10
mythread() ended and the thread id is 0x16fe87000
res.get() is : 5
main ended and the thread id is 0x1000e3d40
Program ended with exit code: 0
相关文章
|
6天前
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
21 3
|
6天前
|
缓存 Java 调度
Java并发编程:深入解析线程池与Future任务
【7月更文挑战第9天】线程池和Future任务是Java并发编程中非常重要的概念。线程池通过重用线程减少了线程创建和销毁的开销,提高了资源利用率。而Future接口则提供了检查异步任务状态和获取任务结果的能力,使得异步编程更加灵活和强大。掌握这些概念,将有助于我们编写出更高效、更可靠的并发程序。
|
6天前
|
Python
解锁Python并发新世界:线程与进程的并行艺术,让你的应用性能翻倍!
【7月更文挑战第9天】并发编程**是同时执行多个任务的技术,提升程序效率。Python的**threading**模块支持多线程,适合IO密集型任务,但受GIL限制。**multiprocessing**模块允许多进程并行,绕过GIL,适用于CPU密集型任务。例如,计算平方和,多线程版本使用`threading`分割工作并同步结果;多进程版本利用`multiprocessing.Pool`分块计算再合并。正确选择能优化应用性能。
|
5天前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
8 0
|
5天前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
18 1
|
5天前
|
数据库 数据安全/隐私保护 C++
Python并发编程实战:线程(threading)VS进程(multiprocessing),谁才是并发之王?
【7月更文挑战第10天】Python并发对比:线程轻量级,适合I/O密集型任务,但受GIL限制;进程绕过GIL,擅CPU密集型,但通信成本高。选择取决于应用场景,线程利于数据共享,进程利于多核利用。并发无“王者”,灵活运用方为上策。
|
5天前
|
设计模式 安全 Java
Java面试题:请解释Java中的线程池以及为什么要使用线程池?请解释Java中的内存模型以及如何避免内存泄漏?请解释Java中的并发工具包以及如何实现一个简单的线程安全队列?
Java面试题:请解释Java中的线程池以及为什么要使用线程池?请解释Java中的内存模型以及如何避免内存泄漏?请解释Java中的并发工具包以及如何实现一个简单的线程安全队列?
10 1
|
5天前
|
设计模式 缓存 安全
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
12 1
|
7天前
|
安全 Java 开发者
Java中的并发工具类与线程安全实现
Java中的并发工具类与线程安全实现
|
5天前
|
设计模式 存储 缓存
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
8 0