c++11并发与多线程【第九节】async、 future、packaged_task、promise

第九节 async、 future、packaged_task、promise

本课程视频链接:https://www.bilibili.com/video/BV1Yb411L7ak?p=11

一、std::async、std::future创建后台任务并返回值,希望线程返回一个结果

std::async是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,返回一个std::future对象,std::future是个类模板。

1.1 什么叫启动一个异步任务?

自动创建一个线程并开始执行对应的入口函数,它返回一个std::future对象,这个std::future对象里就含有线程入口函数所返回的结果(即线程返回的结果),我们可以通过调用std::future对象的成员函数get()来获取结果。

future: 将来,也有人称呼std::future提供了一种访问异步操作结果的机制。就是说这个结果你可能没有办法马上拿到,但是不久的将来,线程执行结束之后,就可以拿到结果。即future对象中会保存一个值,在将来的某个时刻能够拿到。

使用std::asyncstd::future之前需要包含头文件#include<future>

#include<iostream>
#include<vector>
#include<thread>
#include<string>
#include<list>
#include<mutex>
#include <future>

using namespace std;

//线程入口函数
int mythread(){
    cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
    std::chrono::milliseconds dura(5000);//休息5秒钟
    std::this_thread::sleep_for(dura);//休息
    cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
    return 5;
}

int main(){
    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;
    std::future<int> result=std::async(mythread);//自动创建一个新线程,使用mythread()作为入口函数
    cout<<"continue  ...."<<endl;
    cout<<result.get()<<endl;//get()取的是将来的值,mythread还没有执行完,所以主线程会阻塞在这里等待mythread()执行完毕
    cout<<"i love china"<<endl;
    return 0;
}

该程序通过std::future对象的get()成员函数等待线程执行结束并返回结果;
get()函数在拿到返回值之前会一直阻塞。同样的还有future的成员函数wait(),区别在于wait()只等待线程返回,但是不获取返回值。
注意:future::get()只能调用一次。

1.2 使用类成员函数作为可调用对象

class A
{
public:
    //线程入口函数
    int mythread(int mypar){
        cout<<"args = "<<mypar<<endl;
        cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
        std::chrono::milliseconds dura(5000);//休息5秒钟
        std::this_thread::sleep_for(dura);//休息
        cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
        return 5;
    }
};


int main(){
    A a;
    int tempar=12;
    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;
    std::future<int> result=std::async(&A::mythread,&a,tempar);//自动创建一个新线程,使用mythread()作为入口函数,第二个参数是对象引用,才能保证创建线程里的对象和主线程是同一个对象
    cout<<"continue  ...."<<endl;
    cout<<result.get()<<endl;//get()取的是将来的值,mythread还没有执行完,所以主线程会阻塞在这里等待mythread()执行完毕
    //result.wait();
    cout<<"i love china"<<endl;
    return 0;
}

其中自动创建一个新线程,使用mythread()作为入口函数,第二个参数是对象引用,才能保证创建线程里的对象和主线程是同一个对象。

std::future<int> result=std::async(&A::mythread,&a,tempar);

1.3 std::launch类型

通过额外向std::async()传递一个参数,该参数类型的std::lunnch类型(枚举类型),来达到一些目的。

  1. std::launch::deferred 表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行。
    当get()或wait()没有被调用时,线程会执行吗?std::future<int> result=std::async(std::launch::deferred,&A::mythread,&a,tempar); 作为第一个参数。

实际上,如果get()或wait()没有被调用,这个线程根本就不会被创建,更不会执行该入口函数。
如果get()或wait()被调用了并且也不会创建一个新线程,实际上执行该入口函数的还是主线程。

int main(){
    A a;
    int tempar=12;
    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;
    std::future<int> result=std::async(std::launch::deferred,&A::mythread,&a,tempar);//自动创建一个新线程,使用mythread()作为入口函数,第二个参数是对象引用,才能保证创建线程里的对象和主线程是同一个对象
    cout<<"continue  ...."<<endl;
    cout<<result.get()<<endl;//get()取的是将来的值,mythread还没有执行完,所以主线程会阻塞在这里等待mythread()执行完毕
    //result.wait();
    cout<<"i love china"<<endl;
    return 0;
}

两个线程ID都是主线程的ID:

main thread id = 140737348097856
continue ….
args = 12
mythread() start thread id = 140737348097856
mythread() start thread id = 140737348097856
5
i love china

  1. std::launch::async 在调用async函数时就创建新线程;
int main(){
    A a;
    int tempar=12;
    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;
    std::future<int> result=std::async(std::launch::async,&A::mythread,&a,tempar);//自动创建一个新线程,使用mythread()作为入口函数,第二个参数是对象引用,才能保证创建线程里的对象和主线程是同一个对象
    cout<<"continue  ...."<<endl;
    cout<<result.get()<<endl;//get()取的是将来的值,mythread还没有执行完,所以主线程会阻塞在这里等待mythread()执行完毕
    //result.wait();
    cout<<"i love china"<<endl;
    return 0;
}

执行结果:两个线程ID不同

main thread id = 140737348097856
continue ….
args = 12
mythread() start thread id = 140737348093696
mythread() start thread id = 140737348093696
5
i love china

如果同时加上std::launch::async|std::launch::deferred作为参数,不论顺序,均以async为最终取值。

二、std::packaged_task:打包任务,把任务包装起来

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

int mythread(int mypar){
    cout<<"args = "<<mypar<<endl;
    cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
    std::chrono::milliseconds dura(5000);//休息5秒钟
    std::this_thread::sleep_for(dura);//休息
    cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
    return 5;
}

int main(){

    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;
    std::packaged_task<int (int)> mypack(mythread);//packaged_task模板对象是int (int)即入口函数mythread的返回值和参数都是int,在这里把函数mythread通过packaged_task包装起来
    std::thread t1(std::ref(mypack),1);//线程直接开始执行,第二个参数作为线程入口函数的参数
    t1.join();//等待线程执行完毕
    std::future<int> result =mypack.get_future();//std::future对象里含有线程入口函数的返回结果,这里result保存mythread
    cout<<result.get()<<endl;
    return 0;
}

packaged_task模板对象是int (int)即入口函数mythread的返回值和参数都是int,在这里把函数mythread通过packaged_task包装起来.

也可使用lambda表达式达到相同的效果:

int main(){

    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;
    // std::packaged_task<int (int)> mypack(mythread);//packaged_task模板对象是int (int)即入口函数mythread的返回值和参数都是int,在这里把函数mythread通过packaged_task包装起来
    //使用lambda表达式作为入口函数,把入口函数的操作放在下方lambda表达式中
    std::packaged_task<int(int)> mypack([](int mypar){
        cout<<"args = "<<mypar<<endl;
        cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
        std::chrono::milliseconds dura(5000);//休息5秒钟
        std::this_thread::sleep_for(dura);//休息
        cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
        return 5;
    });
    std::thread t1(std::ref(mypack),1);//线程直接开始执行,第二个参数作为线程入口函数的参数
    t1.join();//等待线程执行完毕
    std::future<int> result =mypack.get_future();//std::future对象里含有线程入口函数的返回结果,这里result保存mythread线程的返回值
    cout<<result.get()<<endl;
    return 0;
}

2.2 packaged_task包装起来的可调用对象还可以直接调用,所以从这个角度来说,packaged_task对象也是一个可调用对象。

int main(){

    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;
    // std::packaged_task<int (int)> mypack(mythread);//packaged_task模板对象是int (int)即入口函数mythread的返回值和参数都是int,在这里把函数mythread通过packaged_task包装起来


    //使用lambda表达式作为入口函数
    std::packaged_task<int(int)> mypack([](int mypar)
    {
        cout<<"args = "<<mypar<<endl;
        cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
        std::chrono::milliseconds dura(5000);//休息5秒钟
        std::this_thread::sleep_for(dura);//休息
        cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
        return 5;
    });
    mypack(105);//直接调用lambda表达式,相当于函数调用,并不会再创建线程
    std::future<int> result =mypack.get_future();
    cout<<result.get()<<endl;
    return 0;
}

此时不会创建新线程,线程ID都相同:

main thread id = 140737348097856
args = 105
mythread() start thread id = 140737348097856
mythread() start thread id = 140737348097856
5

2.3 其他用法

vector<std::packaged_task<int(int)>> mytasks;//创建一个容器
int main(){

    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;

    //使用lambda表达式作为入口函数
    std::packaged_task<int(int)> mypack([](int mypar)
    {
        cout<<"args = "<<mypar<<endl;
        cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
        std::chrono::milliseconds dura(5000);//休息5秒钟
        std::this_thread::sleep_for(dura);//休息
        cout<<"mythread() start "<<"thread id = "<<std::this_thread::get_id()<<endl;
        return 5;
    });

    mytasks.push_back(std::move(mypack));//直接把mypack移动到容器中,避免复制过程出错,移动之后mypack就为空
    std::packaged_task<int (int)> mypack2;
    auto iter=mytasks.begin();
    mypack2=std::move(*iter);//用mypack来接着容器中的对象,使用移动语义move    
    mytasks.erase(iter);//删除第一个元素,此时迭代已经失效了,后续不能再使用iter
    mypack2(123);
    std::future<int> result=mypack2.get_future();
    cout<<result.get()<<endl;
    return 0;
}

同样的,使用的都是主线程。

main thread id = 140737348097856
args = 123
mythread() start thread id = 140737348097856
mythread() start thread id = 140737348097856
5

备注:关于这个packaged_task的用法,具体的使用场景老师也没怎么说,不懂的可以自己再查找相关资料,实在不懂以后别乱用就行。

三、std::promise 类模板

我们能够在某个线程中给他赋值,然后在将来某个时刻可以在其他线程中拿出来用.
总结:通过promise保存一个值,在将来某一个时刻我们通过把一个future绑定到这个promise上来得到这个绑定的值。

std::promise<int> myprom;//声明一个promise对象,保存的值类型为int
std::thread t1(mythread,std::ref(myprom),180);
t1.join();//join和thread要配套使用
//获取结果值
std::future<int> ful=myprom.get_future();//promise和future绑定,用于获取线程返回值
auto result=ful.get();//get()只能使用一次
cout<<"result== "<<result<<endl<<" i love china"<<endl;

完整代码:

void mythread(std::promise<int> &temp, int calc)//
{
    //做一些操作
    calc++;
    calc *=10;
    //其他运算
    std::chrono::milliseconds dura(5000);//休息5秒钟
    std::this_thread::sleep_for(dura);//休息
    //计算出结果之后
    int result=calc;//保存结果
    temp.set_value(result);//结果保存到temp这个对象中
}
int main(){

    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;
    std::promise<int> myprom;//声明一个promise对象,保存的值类型为int
    std::thread t1(mythread,std::ref(myprom),180);
    t1.join();
    //获取结果值
    std::future<int> ful=myprom.get_future();//promise和future绑定,用于获取线程返回值
    auto result=ful.get();//get()只能使用一次
    cout<<"result== "<<result<<endl<<" i love china"<<endl;
    return 0;
}

3.1 使用promise在不同线程传递结果

void mythread(std::promise<int> &temp, int calc)//
{
    //做一些操作
    calc++;
    calc *=10;
    //其他运算
    std::chrono::milliseconds dura(5000);//休息5秒钟
    std::this_thread::sleep_for(dura);//休息
    //计算出结果之后
    int result=calc;//保存结果
    temp.set_value(result);//结果保存到temp这个对象中
}

void mythread2(std::future<int> &temp){
    auto result=temp.get();
    cout<<"mythread2 result = "<<result<<endl;
}
int main(){
    cout<<"main thread id = "<<std::this_thread::get_id()<<endl;

    std::promise<int> myprom;//声明一个promise对象,保存的值类型为int
    std::thread t1(mythread,std::ref(myprom),180);
    t1.join();
    //获取结果值
    std::future<int> ful=myprom.get_future();//promise和future绑定,用于获取线程返回值
    //auto result=ful.get();//get()只能使用一次

    std::thread t2(mythread2,std::ref(ful));//即把线程1中的结果拿到线程2中使用
    t2.join();//等待线程2执行完毕
    cout<<" i love china"<<endl;
    return 0;
}

main thread id = 140737348097856
mythread2 result = 1810
i love china

四、总结

学习多线程相关的这些用法,并不是都要用在实际的项目中。
最佳的解决方法其实是用最少最简单的代码写出一个稳定、高效的多线程程序。
编码的过程中,可以多阅读一些高手写的代码,变成自己的积累。
学习这些东西的理由,实际上是为将来一些高手写的代码铺路。

文章内容来源《C++并发与多线程视频课程》

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 lk
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信