14

第二章 线程管理

 5 years ago
source link: https://studygolang.com/articles/19390?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

简述

  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;
      }

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK