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

多线程学习(九)

作者: lxr_ | 来源:发表于2021-11-28 17:14 被阅读0次

    所需头文件

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

    一.std::future的其他成员函数

    上一篇使用了future的get()成员函数,这一篇用范例说明一下其他成员函数,主要是future_status这个枚举类型
    线程入口函数

    int MyThread()
    {
        cout << "MyThread start " << "thread id = " << std::this_thread::get_id() << endl;    //新的线程ID
        std::chrono::milliseconds duration(5000);     //5000ms
        std::this_thread::sleep_for(duration);        //程序停止运行5s
        cout << "MyThread end " << "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);        
        std::future<int> result = std::async(std::launch::deferred,MyThread);     //延迟执行       
    
        cout << "continue..." << endl;
        //cout << result.get() << endl;                //卡在这里等待线程执行完毕
    
        //枚举类型:
        //std::future_status status = result.wait_for(std::chrono::seconds(1)); //希望等待1s就得到返回值
        std::future_status status = result.wait_for(std::chrono::seconds(6));   //希望等待6s就得到返回值
    
        if (status == std::future_status::timeout)     //超时(未返回):表示线程还没执行完
        {
            cout << "线程还未执行完毕" << endl;
        }
        else if (status == std::future_status::ready)  //线程成功返回
        {
            cout << "线程成功执行完毕,返回" << endl;
            cout << result.get() << endl;
        }
        else if (status == std::future_status::deferred)
        {
            //如果async的第一个参数被设置为std::launch::deferred,这本条件成立
            cout << "线程被延迟执行" << endl;
            cout << result.get() << endl;             //调用get后线程入口函数才会执行,且在主线程中执行
        }
    
        cout << "hello world" << endl;
        return 0;
    }
    

    二.std::shared_future:类模板

    get函数复制数据,而不是像std::future那样移动数据,则多个线程就可以获取同一个线程的数据了,即多次调用get也不会报出异常
    下面进行举例测试
    线程入口函数一:

    int MyThread1(int val)
    {
        cout << "MyThread1 start " << "thread id = " << std::this_thread::get_id() << endl;
        std::chrono::milliseconds duration(5000);     //5000ms=5s
        std::this_thread::sleep_for(duration);
        return 5;
    }
    

    线程入口函数二:

    void MyThread2(std::shared_future<int>& tmp)
    {
        cout << "MyThread2 start " << "thread id = " << std::this_thread::get_id() << endl;
        auto result = tmp.get();     //获取值
        
        cout << "MyThread2 result= " << result << endl;
        return;
    }
    

    主函数
    先将线程入口函数一的返回值保存到std::future对象中,再传递给std::shared_future

    int main()
    {
        cout << "main " << "thread id=" << std::this_thread::get_id() << endl;
        std::packaged_task<int(int)> myPT(MyThread1); //把函数MyThread1通过packaged_task包装起来
        std::thread T1(std::ref(myPT), 1);            //线程开始执行,第二个参数作为线程入口函数的参数
        T1.join();                                    //调用join等待线程执行完毕,不用这个的话用get等待程序会崩溃
    
        std::future<int> result = myPT.get_future();  
        bool ifcanget = result.valid();               //valid函数可以得到result是否有有效值,此时应该为true
    
        //std::shared_future<int> result_shared(std::move(result));//需要用std::move转成右值类型
        std::shared_future<int> result_shared(result.share());     //这样也可以,执行完毕后,result_shared中有值,而result里空了 
        ifcanget = result.valid();                    //此时应该为false,result中的值已经移动到result_shared中
    
        auto MyThreadResult = result_shared.get();
        MyThreadResult = result_shared.get();         //可以多次get
    
    
        std::thread T2(MyThread2, std::ref(result_shared));
        T2.join();                                    //等待线程执行完毕
    
        cout << "hello world" << endl;
    
        return 0;
    }
    

    下面我们直接将结果保存到shared_future中,而不借助于future
    修改主函数如下:

    int main()
    {
        cout << "main " << "thread id=" << std::this_thread::get_id() << endl;
        std::packaged_task<int(int)> myPT(MyThread1); //把函数MyThread1通过packaged_task包装起来
        std::thread T1(std::ref(myPT), 1);            //线程开始执行,第二个参数作为线程入口函数的参数
        T1.join();                                    //调用join等待线程执行完毕,不用这个的话用get等待程序会崩溃
    
        //直接保存到shared_future
        std::shared_future<int> result_shared(myPT.get_future());//通过get_future直接构造shared_future对象
    
        auto MyThreadResult = result_shared.get();
        MyThreadResult = result_shared.get();         //可以多次get
    
        std::thread T2(MyThread2, std::ref(result_shared));
        T2.join();                                    //等待线程执行完毕
    
        cout << "hello world" << endl;
    
        return 0;
    }
    

    三.原子操作std::atomic

    3.1)原子操作概念引出范例
    前面的文章讲了互斥量:多线程编程中保护共享数据:锁,操作共享数据,开锁
    互斥量可以解决这样的问题:有两个线程对一个变量进行操作,一个线程,另一个线程向这个变量,从底层汇编代码来看,写操作有很多步骤被打断就会导致意外结果出现
    下面先进行不加互斥量测试

    int global_value = 0;                           //定义一个全局变量
    //线程入口函数
    void MyThread()
    {
        for (int i = 0; i < 1000000; i++)
        {
            global_value++;                         //修改全局变量
        }
    
    }
    

    主函数

    int main()
    {
        thread T1(MyThread);
        thread T2(MyThread);          
        //创建两个线程,都会对全局变量修改
    
        T1.join();
        T2.join();
    
        cout << "两个线程执行完毕,最终结果:" << endl;
        cout << global_value << endl;
    
        return 0;
    }
    

    程序执行结果如下,理想情况下,两个线程都对同一个全局变量进行加操作1000000次,最后的结果应改为2000000,但是经过多次测试,每次的执行结果都不一样,因为出现了上面说的写操作被打断的情况。


    下面进行加互斥量测试:
    int global_value = 0;                           //定义一个全局变量
    std::mutex global_mutex;                        //互斥量
    //线程入口函数
    void MyThread()
    {
        for (int i = 0; i < 1000000; i++)
        {
            global_mutex.lock();                   //加锁
            global_value++;
            global_mutex.unlock();                 //解锁
        }
    
    }
    

    主函数不用修改
    从下面的执行结果可以看出,得到了正确的计算结果。


    如果上述写操作执行的次数再多一些,我们会发现程序执行速度明显变慢,所以有没有什么其他方法可以达到类似于加互斥量的效果呢?就是我们提出的原子操作

    可以把原子操作理解成一种:不需要用互斥量加锁(无锁)技术的多线程并发编程方式
    原子操作是在多线程中不会被打断的程序执行片段比互斥量效率上更胜一筹
    互斥量的加锁一般针对一个代码段(几行代码),而原子操作针对的一般是一个变量
    原子操作,一般都是指“不可分割的操作”,也就是说这种操作状态要么已完成,要么没完成,不会出现中间状态
    std::atomic代表原子操作,std::atomic是一个类模板其实是用来封装某个类型的值
    3.2)基本std::atomic用法范例
    修改int类型的定义即可,并删除有关互斥量的代码,如下:

    std::atomic<int> global_value = 0; //封装了一个类型为int的atomic对象,可以像操作int型变量来操作这个对象
    
    //线程入口函数
    void MyThread()
    {
        for (int i = 0; i < 1000000; i++)
        {
            global_value++;            //对应的操作是原子操作(不会被打断)
        }
    
    }
    

    上述程序运行后的结果如下,可以明显看出运行速度快于互斥量加锁


    下面再举一个栗子
    程序需要实现一个bool变量控制线程退出,故线程一直在运行中查询此变量的状态,而主线程中会对此变量进行修改,此变量需用atomic进行封装
    线程入口函数
    std::atomic<bool> global_ifend = false;  //线程退出标记,原子操作,防止读写乱套
    
    void MyThread()
    {
        std::chrono::milliseconds duration(1000); //1000ms=1s
        while (global_ifend == false)
        {
            //系统没要求程序退出
    
            cout << "thread id= " << std::this_thread::get_id() << "运行中..." << endl;
            std::this_thread::sleep_for(duration);
        }
    
        cout << "thread id= " << std::this_thread::get_id() << "运行结束..." << endl;
        return;
    }
    

    主函数

    int main()
    {
        thread T1(MyThread);
        thread T2(MyThread);
    
        std::chrono::milliseconds duration(5000); //5000ms=5s
        std::this_thread::sleep_for(duration);
    
        global_ifend = true;                      //对原子对象写操作,让线程运行结束
    
        T1.join();
        T2.join();
    
        cout << "程序执行完毕,退出" << endl;
    
        return 0;
    }
    

    以下执行结果为:主函数开始运行5s后修改原子对象的值,进而控制线程退出。


    3.3)总结:
    原子操作一般用于计数或统计(累计发送/接收了多少个数据包)
    其他使用的地方不多

    相关文章

      网友评论

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

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