第二章 线程管理

传说中的水牛 · · 544 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

简述

  1. 如何启动线程
    因启动性程需要一个callable type,可以有函数指针,类重载(),lambda三种形式。
    其中类重载(),lambda,有可能会拿到非运行程序的变量,要小心。
    启动完成后,需要detach或join,否则程序运行会报错。引入一个thread_guard类。
  2. 给线程传参数,会进行拷贝操作。
    如果是引用,则要小心变量的作用域。他只会进行拷贝操作,而不会进行转换操作。
    如果是类,那么又不需要std::ref。
    如果是moveonly的值,在传进去之前要std::move
  3. thread 是一个 moveonly的类型,这里介绍了scoped_thread (它是一个线程的'子类')
    还有,当 thread t1(xxxx); thread t2(x);
    t2 = std::move(t1);会报错,因为t1不见了。
    (经过测试,没有办法再join了,如果有join过,则不会报错)
  4. 其它
    获取当前cpu的核数
    获取当前线程的id
  5. 测试代码

摘录

  1. 启动线程的方式

    1. 函数

      void do_some_work();
      std::thread my_thread(do_some_work);
      
    2. 重载operator()
      经过测试,发现不管用哪种方式都会调用 移动拷贝构造函数
      只是第一种会调用拷贝构造函数一次,移动拷贝构造函数一次。别的都是调用两次移动拷贝构造函数

      class background_task{
          public:
              void operator()()const{
                  //.....
              }
      };
      background_task f;
      std::thread my_thread(f)//书中讲会执行background_task这个类的拷贝构造函数
      //----- 或者
      std::thread my_thread(background_task())//书中讲会执行background_task这个类的移动拷贝构造函数
      std::thread my_thread{background_task()}//书中讲会执行background_task这个类的移动拷贝构造函数
      
    3. lambda

      std::thread my_thread([](){
          //..
      });
      
    4. thread_guard

      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;
      };
      //如何使用
      struct func;
      void f(){
          int some_local_state=0;
          func my_func(some_local_state);
          std::thread t(my_func);
          thread_guard g(t);   //会自动join
          do_something_in_current_thread();
      }
      
  2. 传参数进去

    1. 传参,注意作用域
      经过测试程序并没有崩溃,但是struct func的 int &i这个值的数据确实是跟 some_local_state 的地址一样。
      所以,其实是有问题的 ,只是暂时没崩而已。

      struct func{
          int &i;
          func(int &i_):i(i_){}
          operator()(){
              for ( int j=0; j<1000000;j++ ){ dosomething(i); }
          }
      };
      void oops(){
          int some_local_state=0;
          func my_func(some_local_state); 
          std::thread my_thread(my_func); //在 thread里运行的 &i 数据会没掉
          my_thread.detach();
      }
      
    2. //std::thread t(func,args...) //args默认会进行拷贝操作 是的,会有
      void f(int i, const std::string &str); //如果是  std::string str 呢? 应该也一样,因为问题并没有解决
      void oops(int somevalue){
          char buff[10] = {0};
          sprintf(buff,"%d",somevalue);
          std::thread t(f,somevalue,buff);  //在这里buff会从char[]变成 std::string tmp 
                                            //但不知道是何时操作这个转换,所以有可能是oops已经运行完了,
                                            //所以buff值就不知道变成什么样了。
                                            //解决方法:std::thread t(f,somevalue,std::string(buff));
          t.detach();
      }
      
    3. 当你想把引用传进去,并在thread里面修改值且期望在外面这个值也会有作用,那么:

        void update_data_for_widget(widget_id w, widget_data &data);
        void oops_again(widget_id w){
            widget_data data;
            std::thread t(update_data_for_widget,w,data);
            display_status();
            t.join();
            process_widget(data); //这里的data并没有改变
                                  //原因:std::thread ( func,... ) ...是会把那些参数弄一个temp然后再传进去 (试试是不是会调copy函数)
                                  //解决方法: std::thread t(update_data_for_widget,w,std::ref(data));
        }
    
    1. 如果是一个类和成员函数 my_x 不会执行拷贝
    class X{
        public:
        void do_xxx_work();
    };
    X my_x;
    std::thread t(&X::do_xxx_work,&my_x);
    
    1. 如果一个参数是moveonly的,比如:unique_ptr 需要把那个ptr 给move掉
    void process_big_object(std::unique_ptr<big_object) p);
    std::unique_ptr<big_object> p(new big_object);
    p->prepare_data(42);
    std::thread t(process_big_object,std::move(p));
    
  3. thread 是一个move only的值

    1. scoped_thread
      这个例子放在这里,是因为要std::move 这也是跟thread_guard的区别
      thread_guard是传引用过去。

      class scoped_thread{
          std::thread t;
      public:
          explicit scoped_thread(std::thread t_):t(std::move(t_)){
              if ( !t.joinable() ){
                  throw::std::logic_error("no thread");
              }
          }
          ~scoped_thread(){
              t.join();
          }
          scoped_thread(scoped_thread const &)=delete;
          scoped_thread& operator=(scoped_thread const &)=delete;
      };
      //如何使用
      struct func;
      void f(){
          int some_local_state;
          scoped_thread t(std::thread(func(some_local_state)));
          do_something_in_current_thread();
      }
      
    2. 不过如果有 n个thread

      void do_work(unsigned id);
      void f(){
          std::vector<std::thread> ths;
          for ( i=0;i<20; i++ ){
              ths.push_pack(std::thread(do_work,i));
          }
          std::for_each(ths.begin(),ths.end(),std::mem_fn(&std::thread::join))
      }
      
    3. 可以认为thread是只能移动的,他比只能移动还多一个限制

      void func();
      void other_func();
      std::thread t1(func);
      std::thread t2(other_func);
      t1 = std::move(t2);  //这时还会报错,因为原来的t1,无法join
      
    4. 当作为函数的返回值,是允许的 (详细可以去查看RVO)

      std::thread f(){
          void some_func();
          return std::thread(some_func);
      }
      std::thread g(){
          void some_func();
          std::thread t(some_func);
          return t;
      }
      
    5. 当函数的参数为thread 时 如果是临时变量需要move

      void f(std::thread t);
      //怎么传参:
      void g(){
          //1
          void some_func();
          f(std::thread(some_func));
          //2
          std::thread t(some_func);
          f(std::move(t));
      }
      
    6. 附:golang 的 defer很好用,来个c++版的

      template <typename F> struct privDefer {
          F f;
          privDefer(F fp) : f(fp) {}
          ~privDefer() { f(); }
      };
      template <typename F> privDefer<F> defer_func(F f) { return privDefer<F>(f); }
      #define BUFFALO_DEFER_1(x, y) x##y
      #define BUFFALO_DEFER_2(x, y) BUFFALO_DEFER_1(x, y)
      #define BUFFALO_DEFER_3(x) BUFFALO_DEFER_2(x, __COUNTER__)
      #define defer(code) auto BUFFALO_DEFER_3(_glngbll_defer_val_) = defer_func([&]() { code; })
      //-------------- 使用
      int main(){
          int *a = new int;
          defer(delete a);
          return 0;
      }
      
  4. 杂项

    1. 获取此机器能并行处理多少线程 等于cpu的核数
      std::thread::hard_concurrency();
      
2. 获取thread id
    ```
    //获取id 类型是 std::thread::id;
    std::thread::id master_id;
    if ( std::this_thread::get_id() == master_id){
    }
    ```
  1. 测试代码
    1. 测试拷贝多少次

      #include <iostream>
      #include <thread>
      typedef struct Big_t {
          Big_t() = default;
          Big_t(const Big_t &other) { std::cout << "Big_t" << std::endl; }
          void func() {}
          int v[100];
      } Big_t;
      void funcReference(const Big_t &big) {}
      void funcValue(Big_t big) {}
      int main(int argc, const char *argv[]) {
          Big_t big;
          std::cout << "------------------std::ref 没有construct" << std::endl;
          std::thread t(funcReference, std::ref(big));
          t.join();
          std::cout << "------------------2次construct" << std::endl;
          std::thread t1(funcReference, big);
          t1.join();
          std::cout << "funcValue ------------------std::ref 1次" << std::endl;
          std::thread t10(funcValue, std::ref(big));
          t10.join();
          std::cout << "------------------ 三次" << std::endl;
          std::thread t11(funcValue, big);
          t11.join();
          std::cout << "------------------ class 没有" << std::endl;
          std::thread t2(&Big_t::func, &big);
          t2.join();
          return 0;
      }
      
    2. 测试拷贝函数和移动拷贝函数

      #include <iostream>
      #include <thread>
      class background_task {
       public:
          background_task() = default;
          background_task(const background_task &other) {
              std::cout << "background_task_construct 1" << std::endl;
          }
          background_task &operator=(const background_task &other) {
              std::cout << "background_task_construct 2" << std::endl;
              return *this;
          }
          background_task(background_task &&other) {
              std::cout << "background_task_construct 3" << std::endl;
          }
          background_task &operator=(background_task &&other) {
              std::cout << "background_task_construct 4" << std::endl;
              return *this;
          }
          virtual ~background_task() = default;
       public:
          void operator()() const { std::cout << "thread func" << std::endl; }
      };
      int main(int argc, const char *argv[]) {
          {
              background_task f;
              std::cout << "thread t(f)" << std::endl;
              std::thread t(f);
              t.join();
          }
          std::cout << "-----------------" << std::endl;
          {
              std::cout << "thread t(background_task)" << std::endl;
              std::thread t((background_task()));
              t.join();
          }
          std::cout << "-----------------" << std::endl;
          {
              std::cout << "thread t{background_task()}" << std::endl;
              std::thread t{background_task()};
              t.join();
          }
          std::cout << "-----------------" << std::endl;
          return 0;
      }
      
    3. 看move only的值是否会因没有任何值关联时会报错

      #include <iostream>
      #include <thread>
      void func() { std::cout << "func" << std::endl; }
      void otherfunc() { std::cout << "otherfunc" << std::endl; }
      int main(int argc, const char *argv[]) {
          std::thread t1(func);
          t1.join();
          std::thread t2 = std::move(t1);
          t1 = std::thread(otherfunc);
          t1.join();
          std::thread t3;
          t3 = std::move(t2);
          t1 = std::move(t3);
          return 0;
      }
      

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:传说中的水牛

查看原文:第二章 线程管理

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

544 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传