c++11 多线程编程2 线程管理

简述

  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;
      }
      
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容