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;
      }
      
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容