• c++多线程(一)线程管理


    来源:微信公众号「编程学习基地」

    1.启动线程

    当把函数对象传入到线程构造函数中时,需要避免语法解析问题,不能直接传入函数,而是需要传入函数地址

    #include
    #include
    using namespace std;
    void thread_fun(){
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    }
    
    class workThread{
        public:
        workThread(){}
        void operator()() 
        {
            cout<<"[2]thread Id:"<<std::this_thread::get_id()<<endl;
        }
    };
    
    int main(){
        thread thr(thread_fun);
        workThread workthr2;
        /**
         * @brief 线程类启动线程的三种方式
         */
        // thread thr2(workthr2);
        // thread thr2((workThread()));
        thread thr2{workThread()};
        thr2.join();
        thr.join();
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2.等待线程完成

    std::thread实例需要使用join(),可以确保局部变量在线程完成后被销毁。join()是简单粗暴的等待线程完成或不等待。只能对一个线程使用一次join();一旦已经使用过join()std::thread对象就不能再次加入了,当对其使用joinable()时,将返回false)。

    #include
    #include
    using namespace std;
    void thread_fun(){
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    }
    int main(){
        thread thr(thread_fun);
        if(thr.joinable())
        {
            thr.join();
        }
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    2.1特殊情况下的等待

    需要对一个还未销毁的std::thread对象使用join()detach()。如果想要分离一个线程,可以在线程启动后,直接使用**detach()**进行分离。如果打算等待对应线程,需要选择一个合适的位置。例如当出现异常并抛出异常后未正确调用线程等待,需要在异常处理过程中调用异常。

    void f()
    {
      int some_local_state=0;
      func my_func(some_local_state);
      std::thread t(my_func);	//开启线程
      try
      {
        do_something_in_current_thread();	//执行其他的一些任务
      }
      catch(...)	//执行其他任务时出现异常捕获后抛出异常前需要等待线程结束
      {
        t.join();  // 1
        throw;
      }
      t.join();  // 2
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可看可不看,不重要…

    2.2使用RAII等待线程完成

    RAII是Resource Acquisition Is Initialization(wiki上面翻译成 “资源获取就是初始化”)的简称,是C++语言的一种管理资源、避免泄漏的惯用法。利用的就是C++构造的对象最终会被销毁的原则。RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。

    #include
    #include
    using namespace std;
    class thread_guard
    {
        std::thread& t;
    public:
        explicit thread_guard(std::thread& t_):
            t(t_)
        {}
        ~thread_guard()
        {
            if(t.joinable())
            {
                t.join();
            }
        }
        thread_guard(thread_guard const&)=delete;
        thread_guard& operator=(thread_guard const&)=delete;
    };
    void thread_fun(){
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    }
    int main(){
        thread thr(thread_fun);
        thread_guard guard(thr);
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    拷贝构造函数和拷贝赋值操作被标记为=delete,是为了不让编译器自动生成它们。直接对一个对象进行拷贝或赋值是危险的,因为这可能会弄丢已经加入的线程。通过删除声明,任何尝试给thread_guard对象赋值的操作都会引发一个编译错误。

    2.3后台运行线程

    使用detach()会让线程在后台运行,这就意味着主线程不能与之产生直接交互。也就是说,不会等待这个线程结束;如果线程分离,那么就不可能有std::thread对象能引用它,分离线程的确在后台运行,所以分离线程不能被加入。

    通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,且没有任何用户接口,并在后台运行的线程。

    std::thread t(do_background_work);
    t.detach();
    assert(!t.joinable());
    
    • 1
    • 2
    • 3
    2.4量产线程,等待结束

    通过标准中的std::vector<>量产线程并且等待他们结束

    #include 
    #include 
    #include 
    #include 
    #include    //mem_fn
    #include     //for_each
    using namespace std;
    template<typename Iterator,typename T>
    struct accumulate_block
    {
        void operator()(Iterator first,Iterator last,T& result)
        {
            result=std::accumulate(first,last,result);
        }
    };
    struct MyAccumulate
    {
        void operator()(vector<int>::iterator begin,vector<int>::iterator end,int& result)
        {
            result=std::accumulate(begin,end,result);
        }
    };
    void fun(vector<int>::iterator begin,vector<int>::iterator end,int& result){
        result = std::accumulate(begin, end, result);
    }
    int main(){
        //定义完成任务的线程数量
        int thread_num = 4;
        vector<int> vec;
        //任务:有一个100大小容器,需要统计容器中数量的和
        for(int i=0;i<100;i++)
            vec.push_back(i + 1);
        //计算每个线程需要计算的快大小
        int block_size = 100 / thread_num;
        //统计任务结果集,每个线程统计一个结果
        vector<int> results(thread_num);
        vector<thread> threads(thread_num); 
        vector<int>::iterator block_begin = vec.begin();
        for(int i=0;i<thread_num;i++){
            vector<int>::iterator block_end = block_begin;
            //将迭代器往后移动 block_size 大小
            std::advance(block_end,block_size);
            MyAccumulate tmp;
            // threads[i] = std::thread(accumulate_block::iterator,int>(),block_begin,block_end,std::ref(results[i]));
            threads[i] = std::thread(fun, block_begin, block_end, std::ref(results[i]));
            block_begin = block_end;
        }
        for_each(threads.begin(),threads.end(),std::mem_fn(&thread::join));
        int result = std::accumulate(results.begin(), results.end(), 0);
        cout << "result:" << result <<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    该示例主要是演示创建多个线程分配任务后对计算结果进行统计。

    2.传递参数

    因为线程的创建属于函数式编程,所以传递的参数都会被拷贝一份,传入参数时尽可能传入指针

    #include
    #include
    using namespace std;
    void thread_fun(int* arg){
        int tmp = *(int*)arg;
        cout<<"thread Id:"<<std::this_thread::get_id()<< ",tmp:"<<tmp << endl;
    }
    class workThread{
        int m_val;
        public:
        workThread(int val){
            m_val = val;
        }
        void operator()() 
        {
            cout<<"thread Id:"<<std::this_thread::get_id()<<","<<m_val<<endl;
        }
    };
    int main(){
        int arg = 10;
        thread thr(thread_fun,&arg);
        thr.join();
        workThread workthr2(arg);
        // std::thread thr2(workthr2);
        // std::thread thr2((workThread(arg)));
        std::thread thr2{(workThread(arg))};
        thr2.join();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    当需要传递引用时,构造函数无视函数期待的参数类型,并盲目的拷贝已提供的变量,可以使用std::ref将参数转换成引用的形式。

    #include
    #include
    using namespace std;
    void thread_fun2(int &arg){
        arg = 20;
        cout<<"thread Id:"<<std::this_thread::get_id()<< ",tmp:"<<arg << endl;
    }
    int main(){
        int arg = 10;
        thread thr(thread_fun2,std::ref(arg));
        thr.join();
        cout << "thread Id:" << std::this_thread::get_id() << ",arg:" << arg << endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    thread Id:139977403291392,tmp:20
    thread Id:139977420166976,arg:20
    
    • 1
    • 2

    3.转移线程所有权

    C++标准库中有很多资源占有(resource-owning)类型,比如std::ifstream,std::unique_ptr还有std::thread都是可移动(movable),但不可拷贝(cpoyable)。这就说明执行线程的所有权可以在std::thread实例中移动。

    #include
    #include
    using namespace std;
    void thread_fun2(int &arg){
        arg = 20;
        cout<<"thread Id:"<<std::this_thread::get_id()<< ",tmp:"<<arg << endl;
    }
    int main(){
        int arg = 10;
        thread thr(thread_fun2,std::ref(arg));
        thread thr2 = std::move(thr);
        if(thr2.joinable()){
            cout<<"thr2 joinable.."<<endl;
            thr2.join();
        }
        cout << "thread Id:" << std::this_thread::get_id() << ",arg:" << arg << endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    当所有权可以在函数内部传递,就允许std::thread实例可作为参数进行传递,std::thread支持移动的好处是可以创建thread_guard类的实例,并且拥有其线程的所有权。

    为了确保线程程序退出前完成,下面的代码里定义了thread_guard类

    #include 
    #include 
    using namespace std;
    class thread_guard
    {
        std::thread& t;
    public:
        explicit thread_guard(std::thread& t_):
            t(t_)
        {
            if(!t.joinable())
                throw std::logic_error("No thread");
        }
        ~thread_guard()
        {
            t.join();
        }
        thread_guard(thread_guard const&)=delete;
        thread_guard& operator=(thread_guard const&)=delete;
    };
    void thread_fun(){
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    }
    int main(){
        thread thr(thread_fun);
        thread_guard guard(thr);
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    4.运行时决定线程数量

    std::thread::hardware_concurrency()在新版C++标准库中是一个很有用的函数。这个函数将返回能同时并发在一个程序中的线程数量。例如,多核系统中,返回值可以是CPU核芯的数量。返回值也仅仅是一个提示,当系统信息无法获取时,函数也会返回0。但是,这也无法掩盖这个函数对启动线程数量的帮助。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    using namespace std;
    
    template<typename Iterator,typename T>
    struct accumulate_block
    {
        void operator()(Iterator first,Iterator last,T& result)
        {
            result=std::accumulate(first,last,result);
        }
    };
    
    template<typename Iterator,typename T>
    T parallel_accumulate(Iterator first,Iterator last,T init)
    {
        unsigned long const length=std::distance(first,last);   //该函数会返回[first, last)范围内包含的元素的个数
    
        if(!length)
            return init;
    
        unsigned long const min_per_thread=25;  //定义每个线程计算的任务量
        unsigned long const max_threads=        //通过每个线程计算的任务量和总任务计算所需线程数量,-1的原因是保证最少需要一个任务才能启动一个线程
            (length+min_per_thread-1)/min_per_thread;
        cout<<"max thread:"<<max_threads<<endl;
        unsigned long const hardware_threads=
            std::thread::hardware_concurrency();     //返回能同时并发在一个程序中的线程数量,例如,多核系统中,返回值可以是CPU核芯的数量。
    
        cout<<"hardware_threads:"<<hardware_threads<<endl;
        unsigned long const num_threads=
            std::min(hardware_threads!=0?hardware_threads:2,max_threads);
        cout<<"num_threads:"<<num_threads<<endl;
        unsigned long const block_size=length/num_threads;
    
        std::vector<T> results(num_threads);
        std::vector<std::thread>  threads(num_threads-1);   //线程数量减一的原因是当前线程也参与计算,
    
        Iterator block_start=first;
        for(unsigned long i=0;i<(num_threads-1);++i)
        {
            Iterator block_end=block_start;
            //将迭代器往后移动 block_size 大小
            std::advance(block_end,block_size);
            threads[i]=std::thread(
                accumulate_block<Iterator,T>(),
                block_start,block_end,std::ref(results[i]));
            block_start=block_end;
        }
        //当前线程也参与计算部分任务量
        accumulate_block<Iterator,T>()(block_start,last,results[num_threads-1]);
        
        std::for_each(threads.begin(),threads.end(),
            std::mem_fn(&std::thread::join));
    
        return std::accumulate(results.begin(),results.end(),init);
    }
    
    int main()
    {
        std::vector<int> vi;
        for(int i=0;i<100;++i)
        {
            vi.push_back(10);
        }
        int sum=parallel_accumulate(vi.begin(),vi.end(),5);
        std::cout<<"sum="<<sum<<std::endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    [root@zs3-2 lesson1]# ./main
    max thread:4
    hardware_threads:32
    num_threads:4
    sum=1005
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这个demo有点苦涩难懂,多花点时间,结合2.4量产线程,等待线程结束一起思考更好。

    5. 识别线程

    线程标识类型是std::thread::id,可以通过两种方式进行检索。第一种,可以通过调用std::thread对象的成员函数get_id()来直接获取,注意一定要先有std::thread对象哦。第二种,当前线程中调用std::this_thread::get_id()(这个函数定义在头文件中)也可以获得线程标识。

    #include
    #include
    using namespace std;
    void thread_fun(){
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
    }
    
    int main(){
        thread thr(thread_fun);
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        cout<<"thr id:"<<thr.get_id()<<endl;
        thr.join();
        cout<<"thr id:"<<thr.get_id()<<endl;    //返回std::thread::type默认构造值,这个值表示“没有线程”
        cout<<"thread Id:"<<std::this_thread::get_id()<<endl;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    1. SAP Business Application Studio 里创建一个基于 CAP 模型的最简单的 OData 服务
    线性代数笔记8--AX=b:可解性、解的结构
    入职美团Java岗后,美团人事分享给了我一份面试笔记......
    elasticsearch-head 连接ES出现401问题解决
    售后服务管理系统(Java+Web+J2EE+MySQL)
    探索无限自然之美——Terragen Professional 4渲染软件
    web网页设计期末课程大作业:水果网站设计——HTML+CSS+JavaScript水果超市(带论文)
    1330_硬件测试中的BCI测试
    JVM进阶
    基于云边协同架构的五大应用场景革新
  • 原文地址:https://blog.csdn.net/qq_44519484/article/details/127906442