美文网首页
多线程学习(八)

多线程学习(八)

作者: lxr_ | 来源:发表于2021-11-27 11:57 被阅读0次

所需头文件

#include "stdafx.h"
#include <iostream>
#include <thread>
#include <future>
#include <vector>
using namespace std;

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

有时候我们希望线程返回一个结果,因为线程本身也执行的是一个函数
std::async是个函数模板,用来启动一个异步任务,启动起来一个异步任务后,返回一个std::future(类模板)对象
启动一个异步任务:就是自动创建与一个线程开始执行对应的线程入口函数,它返回一个std::future对象,
std::future对象里边含有线程入口函数所返回的结果线程返回的结果),可以通过调用future对象的成员函数get()来获取结果
"future:"将来的意思,有人也称呼std::future提供了一种访问一步操作结果的机制,也就是这个结果可能没有办法马上拿到,但不久的将来在线程执行完毕的时候,就能够拿到结果
future(对象)里会保存一个值,在将来的某个时刻能够拿到
下列程序通过std::future对象的get()成员函数等待线程结束并返回结果未拿到返回值就一直等待

线程入口函数

int MyThread()
{
    cout << "MyThread() start" << " thread id=" << std::this_thread::get_id() << endl; //打印线程ID
    std::chrono::milliseconds duration(5000);                                          //停止5秒
    std::this_thread::sleep_for(duration);

    cout << "MyThread() end" << " thread id=" << std::this_thread::get_id() << endl;   //打印线程ID
    return 5;
}

类成员函数作为线程入口函数

class A
{
public:
    int AThread(int myVal)
    {
        cout << myVal << endl;

        cout << "AThread() start" << " thread id=" << std::this_thread::get_id() << endl; //打印线程ID
        std::chrono::milliseconds duration(5000);                                      //停止5秒
        std::this_thread::sleep_for(duration);

        cout << "AThread() end" << " thread id=" << std::this_thread::get_id() << endl;   //打印线程ID
        return 5;
    }
};

主函数

int main()
{
    A obj;                               //创建A对象
    int val = 12;                        //成员函数所需参数

    cout << "main" << " thread id=" << std::this_thread::get_id() << endl;//打印主线程ID

    //std::future<int> result = std::async(MyThread);            
    //创建线程并开始执行(普通函数作为线程入口函数)

    std::future<int> result = std::async(&A::AThread, &obj, val);        
    //类成员函数作为线程入口函数,第二个参数是对象引用,才能保证线程里用的是同一个对象
 
    //执行其他程序
    cout << "continue....!" << endl;
    int def;
    def = 0;

    cout << result.get() << endl;                        //卡在这里等待MyThread()执行完毕,拿到结果
    //cout << result.get() << endl;                      //get()只能调用一次,不能调用多次

    //result.wait();                                     //等待线程返回,本身并不返回结果
    cout << "Hello World!" << endl;

    return 0;
}

我们通过额外向std::async()传递一个参数,该参数类型是std::launch类型枚举类型),来达到一些特殊的目的
a)std::launch::deferred表示线程入口函数调用被延迟到std::future的wait()或者get()函数调用时才执行
如果wait()或者get()没有被调用,那么线程会执行吗?没执行,实际上线程根本就没有创建(可以自行测试,会发现主线程立即返回)。
修改main函数并进行测试

int main()
{
    A obj;
    int val = 12;

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

    std::future<int> result = std::async(std::launch::deferred,&A::AThread, &obj, val);        
    //加入std::launch::defered参数

    //执行其他程序
    cout << "continue....!" << endl;
    int def;
    def = 0;

    cout << result.get() << endl;  //线程入口函数开始执行,卡在这里等待MyThread()执行完毕,拿到结果

    //result.wait();               
    cout << "Hello World!" << endl;

    return 0;
}

根据上图的程序执行结果,会发现主线程与新线程ID相同,所以根本没有创建新线程延迟调用相当于串行执行程序,没有必要创建新线程)
std::launch::deferred:延迟调用,并且没有创建新的线程,是在主线程中调用的线程入口函数
b)std::launch::async,在调用async函数的时候开始创建线程
std::future<int> result = std::async(std::launch::async, &A::AThread, &obj, val);

其实async()函数模板默认用的就是std::launch::async参数,所以加与不加这个参数都是一样的效果,这里可以自行测试**
上面这句话是错误的,其实默认参数是std::launch::async|std::launch::deferred,即联合使用,具体说明可以看多线程学习(十)

另一个问题:两个参数可以联合使用吗?std::future<int> result = std::async(std::launch::async|std::launch::deferred, &A::AThread, &obj, val);
即实现在创建的新线程后延迟执行线程入口函数
在下面的程序通过加断点调试发现,线程入口函数不会等到get()函数调用的时候才开始执行,而在调用async函数的时候就开始执行

int main()
{
    A obj;
    int val = 12;

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

    //std::future<int> result = std::async(MyThread);            //创建线程并开始执行
    std::future<int> result = std::async( std::launch::deferred | std::launch::async , &A::AThread, &obj, val);
    //加入std::launch::deferred | std::launch::async参数

    //执行其他程序
    cout << "continue....!" << endl;
    cout << "continue....!" << endl;
    cout << "continue....!" << endl;
    cout << "continue....!" << endl;
    cout << "continue....!" << endl;
    cout << "continue....!" << endl;
    cout << "continue....!" << endl;
    cout << "continue....!" << endl;
    int def;
    def = 0;

    cout << result.get() << endl;     //卡在这里等待MyThread()执行完毕,拿到结果
                                             
    cout << "Hello World!" << endl;

    return 0;
}

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

std::packaged_task是个类模板,它的参数是可调用对象;通过std::packaged_task把各种可调用对象包装起来,方便将来作为线程入口函数来调用
下面测试中将普通的函数包装起来后面作为线程入口函数调用。
线程入口函数

int MyThread(int val)
{
    cout << val << endl;
    cout << "MyThread() start" << " thread id=" << std::this_thread::get_id() << endl; //打印线程ID
    std::chrono::milliseconds duration(5000);                                          //停止5秒
    std::this_thread::sleep_for(duration);

    cout << "MyThread() end" << " thread id=" << std::this_thread::get_id() << endl;   //打印线程ID
    return 5;
}

主函数

int main()
{
    cout << "main" << " thread id=" << std::this_thread::get_id() << endl;
    std::packaged_task<int(int)> myPT(MyThread);      //把线程入口函数通过packaged_task包装起来
    //其中<int(int)>第一个int为线程入口函数MyThread的返回值类型,第二个参数int为线程入口函数MyThread的参数类型

    std::thread T1(std::ref(myPT),1);                 //使用该std::packaged_task类型创建线程并开始执行
    //第二个参数“1”为线程入口函数MyThread的参数

    T1.join();                                        //等待线程执行完毕

    std::future<int> result = myPT.get_future();      //result保存线程入口函数MyThread返回的结果

    cout << result.get() << endl;
    
    cout << "Hello World!" << endl;

    return 0;
}

既然可以包装可调用对象,那么也可以包装lambda表达式
测试如下:

int main()
{
    cout << "main" << " thread id=" << std::this_thread::get_id() << endl;
    std::packaged_task<int(int)> myPT([](int val) {    //把lambda表达式通过packaged_task包装起来
        cout << val << endl;
        cout << "MyThread() start" << " thread id=" << std::this_thread::get_id() << endl; //打印线程ID
        std::chrono::milliseconds duration(5000);                                          //停止5秒
        std::this_thread::sleep_for(duration);

        cout << "MyThread() end" << " thread id=" << std::this_thread::get_id() << endl;   //打印线程ID
        return 5;
    });      
    //其中<int(int)>第一个int为lambda表达式的返回值类型,第二个参数int为lambda表达式的参数类型

    std::thread T1(std::ref(myPT),1);                 //使用该std::packaged_task类型创建线程并开始执行
    //第二个参数“1”为lambda表达式的参数

    T1.join();                                        //等待线程执行完毕

    std::future<int> result = myPT.get_future();      //result保存lambda返回的结果

    cout << result.get() << endl;
    
    cout << "Hello World!" << endl;

    return 0;
}

packaged_task包装起来的可调用对象可以直接调用,不用创建线程
举例如下:

int main()
{
    cout << "main" << " thread id=" << std::this_thread::get_id() << endl;
    std::packaged_task<int(int)> myPT([](int val) {    //把lambda表达式通过packaged_task包装起来
        cout << val << endl;
        cout << "MyThread() start" << " thread id=" << std::this_thread::get_id() << endl; //打印线程ID
        std::chrono::milliseconds duration(5000);                                          //停止5秒
        std::this_thread::sleep_for(duration);

        cout << "MyThread() end" << " thread id=" << std::this_thread::get_id() << endl;   //打印线程ID
        return 5;
    });
    //其中<int(int)>第一个int为lambda表达式的返回值类型,第二个参数int为lambda表达式的参数类型

    myPT(100);                                        //直接调用,不创建线程,相当于函数调用

    std::future<int> result = myPT.get_future();      //result保存线程入口函数MyThread返回的结果

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

    cout << "Hello World!" << endl;

    return 0;
}

//其他使用例子

vector <std::packaged_task<int(int)>> myTasks;      //容器存放packaged_task对象
int main()
{
    cout << "main" << " thread id=" << std::this_thread::get_id() << endl;
    std::packaged_task<int(int)> myPT([](int val) {    //把lambda表达式通过packaged_task包装起来
        cout << val << endl;
        cout << "MyThread() start" << " thread id=" << std::this_thread::get_id() << endl; //打印线程ID
        std::chrono::milliseconds duration(5000);                                          //停止5秒
        std::this_thread::sleep_for(duration);

        cout << "MyThread() end" << " thread id=" << std::this_thread::get_id() << endl;   //打印线程ID
        return 5;
    });
    //其中<int(int)>第一个int为lambda表达式的返回值类型,第二个参数int为lambda表达式的参数类型

    myTasks.push_back(std::move(myPT));           //入容器,这里用了移动语义(入进去之后myPT就为空了)

    std::packaged_task<int(int)> PT;
    auto iter = myTasks.begin();
    PT = std::move(*iter);                        //移动语义
    //此句执行后,iter指向的packaged_task对象中的函数为空,但size仍为1,后面移除

    myTasks.erase(iter);                          //删除第一个元素,迭代器已经失效,后续代码将不能再使用iter

    PT(100);                                      //直接调用

    std::future<int> result = PT.get_future();      //result保存线程入口函数MyThread返回的结果
    
    cout << result.get() << endl;
    
    cout << "Hello World!" << endl;
    
    return 0;
}

三.std::promise,类模板

能够在某个线程中给它赋值,在其他线程中取出来使用
线程入口函数

void MyThread(std::promise<int>& tmp, int calc) //<int>中的int为后续要set_value的值的类型
{
    //执行一系列程序
    calc++;
    calc *= 10;

    //比如整整花费了5秒钟
    std::chrono::milliseconds duration(5000);      //停止5秒
    std::this_thread::sleep_for(duration);

    //假设计算得到结果
    int result = calc;                             //保存结果
    tmp.set_value(result);                         //结果保存到tmp对象中

    return;
}

主函数

int main()
{
    std:promise<int> myPromise;                   //声明std::promise对象,其保存的值类型为int类型
    std::thread T1(MyThread, std::ref(myPromise), 100);
    T1.join();           //用thread对象创建线程,如果不用join等待而用后面的get()等待,程序运行异常

    //获取结果值
    std::future<int> fu1 = myPromise.get_future();//promise和future绑定,用于获取线程返回值
    auto result = fu1.get();                      //get只能调用一次

    cout << "result=" << result << endl;
    cout << "hello world!" << endl;

    return 0;
}

总结:通过promise保存一个值,在将来某个时刻通过future绑定到这个promise上来得到这个绑定的值
也可以在另一个线程获取值,举例如下:
再创建一个线程入口函数

void MyThread2(std::future<int>& tmp)
{
    auto result = tmp.get();
    cout << "MyThread2 result=" << result << endl;
    return;
}

主函数

int main()
{
    std:promise<int> myPromise;                   //声明std::promise对象,其保存的值类型为int类型
    std::thread T1(MyThread, std::ref(myPromise), 100);
    T1.join();           //用thread对象创建线程,如果不用join等待而用后面的get()等待,程序运行异常

                                                  //获取结果值
    std::future<int> fu = myPromise.get_future(); //promise和future绑定,用于获取线程返回值
    
    std::thread T2(MyThread2, std::ref(fu));      //线程2中使用线程1中的结果
    T2.join();                                    //等MyThread2执行完毕

    cout << "hello world!" << endl;

    return 0;
}

四.小结

到底怎么用,什么时候用:
我们学习这些东西的目的,并不是要把他们都用在自己的实际开发中
相反,如果我们能够用最少的东西写出用一个稳定、高效的多线程程序,更值得赞赏
为了成长,必须阅读一些高手写的代码,从而快速实现自己代码的积累,我们的技术会有大幅提升
将学习这些内容的理由解释为:为我们将来能够读懂大师的代码铺路

相关文章

网友评论

      本文标题:多线程学习(八)

      本文链接:https://www.haomeiwen.com/subject/piixxrtx.html