• c++并发编程/多线程 thread 库


    系列文章目录



    -进程

    进程是运行着的程序

    进程内存空间分配:略

    如果主进程结束而子进程未结束,则Linux内核会将该子进程的父进程ID改为1(init进程),

    -前言

    void funcname(const A& v);

    std::thread(funcname, value); // 即使函数的形参是引用类型也会发生拷贝构造,
    除非:
    void funcname(A& v);
    std::thread(funcname, std::ref(value)); // 这样value对象就是主线程中的对象

    base类

    #include 
    #include 
    
    class Base
    {
    public:
        int num = 1;  // 类内初始化
        Base() 
        { 
            std::cout << "默认构造函数Base()运行 "
            << " this: " << this << " id= "
            << std::this_thread::get_id() << std::endl;
        }
        Base(const Base& b)
        {
            std::cout << "拷贝构造函数Base(&)"
            << " this: " << this << " id= "
            << std::this_thread::get_id() << std::endl;
        }
        ~Base()
        {
            std::cout << "析构函数~Base()"
            << " this: " << this << " id= "
            << std::this_thread::get_id() << std::endl;
        }
        void operator()(int num)
        {
            std::cout << "运算符重载,子线程执行: "
            << " this: " << this << " id= "
            << std::this_thread::get_id() << std::endl;
        }
        void thdjob(int n);
    };
    
    • 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

    线程执行函数

    1. 普通函数
      void thdjob(int n)
      {
          std::cout << "子线程执行:" 
                    << std::this_thread::get_id() 
                    << std::endl;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    2. 类的成员函数
      void Base::thdjob(int n)
       {
           std::cout << "子线程执行:" 
                     << std::this_thread::get_id() 
                     << std::endl;
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    结果分析

    int main()
    {
        Base b;
        // 在第一个参数为普通函数的情况下,引用?
        // 当第一个参数为类的成员函数时,则子线程和主线程用的不是同一个对象
        // 若为引用或地址则为同一个对象
        std::thread thd(&Base::thdjob, b, 4);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    小结,行为总结

    • std::thread中,即使线程函数的形参是引用类型也会进行对象拷贝
    • std::thread(…)中假定所有实参都为右值
      void thdjob(const Base& b); // 必须是const引用,并且会发生
      
      • 1
    • 无对象拷贝的方式:
      1. 引用
         void thdjob(Base& b);
         // 子线程中的对象b与主线程中的是同一个,自然无对象拷贝
         // 需要确保子线程在使用b时,主线程不会将其销毁
         std::thread th(thdjob, std::ref(b));  // std::ref将b变为引用类型
        
        • 1
        • 2
        • 3
        • 4
      2. 指针
         void thdjob(Base* b);
         // 用地址传递自然都是同一个对象
         std::thread th(thdjob, &b);
        
        • 1
        • 2
        • 3
    • 发生对象拷贝
      1.  2次拷贝,主线程子线程各一次
        void thdjob(Base b);
        std::thread th(thdjob, b);
        
        • 1
        • 2
        发生两次对象拷贝,第一次发生在主线程,将b对象拷贝到th,第二次发生在子线程,将th中的右值对象拷贝到形参
        默认构造函数Base()运行  this: 0x7ffcd8b7f014 id= 139770366710720
        ----------
        拷贝构造函数Base(&) this: 0x558e2431d2c8 id= 139770366710720
        拷贝构造函数Base(&) this: 0x7f1ed29fed74 id= 139770359445056
        this: 0x7f1ed29fed74子线程执行:139770359445056
        
        • 1
        • 2
        • 3
        • 4
        • 5
      2.  1次拷贝
        // 子线程发生一次对象拷贝
        void thdjob(Base b);
        // std::ref  主线程无拷贝
        std::thread th(thdjob, std::ref(b));
        
        • 1
        • 2
        • 3
        • 4
        默认构造函数Base()运行  this: 0x7ffd5c67123c id= 139786634453952
        ----------
        拷贝构造函数Base(&) this: 0x7f229c3fed74 id= 139786627053120
        this: 0x7f229c3fed74子线程执行:139786627053120
        
        • 1
        • 2
        • 3
        • 4

        推荐的方式
        // 子线程无拷贝
        void thdjob(const Base& b);
        // 主线程进行1次对象拷贝
        std::thread th(thdjob, b);
        
        • 1
        • 2
        • 3
        • 4
        默认构造函数Base()运行  this: 0x7ffd26fb3764 id= 140183106712512
        ----------
        拷贝构造函数Base(&) this: 0x563e24eee2c8 id= 140183106712512
        this: 0x563e24eee2c8子线程执行:140183099930176
        
        • 1
        • 2
        • 3
        • 4
        解析:主线程创建b的拷贝,即使主线程结束也是安全的;子线程引用b的拷贝,当子线程结束时负责析构该对象。

    -c++11线程对象创建后既不join()也不detach()的后果

    c++11中,创建对象(std::thread)后有两种状态:

    joinable
    
    nonjoinable
    
    • 1
    • 2
    • 3

    线程对象通过默认构造函数构造后状态为nonjoinable; 线程对象通过有参构造函数创建后状态为join able。joinable状态的线程对象被调用join()或detach()会变为nonjoinable状态。

    线程对象析构

    // thread类中的析构函数定义:
    ~thread()
    {
        if(nonjoinable){
            std::terminate();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    线程对象析构时,会判断线程的状态。如果线程处于join able状态时,会调用terminate()函数直接令程序退出。

    也就是说,创建一个可运行(创建时传入线程函数)线程对象后,必须对该对象进行处理,要么调用join()要么调用detach(),否则线程对象析构时程序将直接退出。

    -


    附注代码

    推荐的方式

    void testfn(const Base & b)
    {
        std::cout << "this = " << &b << " tid = " << std::this_thread::get_id()
                  << std::endl;
        usleep(10000000);
        std::cout << b.num << std::endl;
    }
    
    void subth()
    {
        Base B;
        std::cout << "subth Base B this =  " << &B 
                  << " tid = " << std::this_thread::get_id() << std::endl;
        std::thread th(testfn, B);
        th.detach();
    }
    
    int main()
    {
      std::cout << "main tid: " << std::this_thread::get_id() << std::endl;
      std::thread th(subth);
      th.detach();
      std::cout << "main sleep" << std::endl;
      while(1);
    }
    
    • 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
    main tid: 140303695139776
    main sleep
    默认构造函数Base()运行  this: 0x7f9aff7fed6c id= 140303688267328
    subth Base B this =  0x7f9aff7fed6c tid = 140303688267328
    拷贝构造函数Base(&) this: 0x7f9af8000b78 id= 140303688267328
    析构函数~Base() this: 0x7f9aff7fed6c id= 140303688267328
    this = 0x7f9af8000b78 tid = 140303679874624
    1
    析构函数~Base() this: 0x7f9af8000b78 id= 140303679874624
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    字符串的认识
    挑战视觉边界,探索图形验证码背后的黑科技
    Python 潮流周刊#18:Flask、Streamlit、Polars 的学习教程
    个人微信api
    【Linux】进程通信 | 信号
    C++ —— Tinyxml2在Vs2017下相关使用2(较文1更复杂,附源码)
    高性能数据访问中间件 OBProxy(四):一文讲透连接管理
    [MySQL]-xtabackup搭建主从
    Java学习笔记:SQLite数据库
    图神经网络论文笔记(一)——北邮:基于学习解纠缠因果子结构的图神经网络去偏
  • 原文地址:https://blog.csdn.net/surfaceyan/article/details/130108432