• 【C++多线程】传递参数


      线程可以共享进程的内存空间,线程拥有自己独立内存。

      关于参数的传递,std::thread的构造函数只会单纯的复制传入的变量,特别需要注意的是传递引用时,传入的是值的副本,也就是说子线程中的修改影响不了主线程中的值。

    值传递

      主线程中的值,被拷贝一份传到了子线程中。

     1 #include <iostream>
     2 #include <thread>
     3 
     4 using namespace std;
     5 
     6 void test(int ti, int tj)
     7 {
     8     cout << "子线程开始" << endl;
     9     //ti的内存地址0x0055f69c {4},tj的内存地址0x0055f6a0 {5}
    10     cout << ti << " " << tj << endl;  
    11     cout << "子线程结束" << endl;
    12     return;
    13 }
    14 
    15 
    16 int main()
    17 {
    18     cout << "主线程开始" << endl;
    19     //i的内存地址0x001efdfc {4},j的内存地址0x001efdf0 {5}
    20     int i = 4, j = 5;  
    21     thread t(test, i, j);
    22     t.join();
    23     cout << "主线程结束!" << endl;
    24     return 0;
    25 }

    传引用

      从下面的运行结果,可以看出,即使是用引用来接收传的值,也是会将其拷贝一份到子线程的独立内存中,这一点与我们编写普通程序时不同。这是因为线程的创建属于函数式编程,所以为了传引用C++中才引入了std::ref()。关于std::ref()

     1 #include <iostream>
     2 #include <thread>
     3 
     4 using namespace std;
     5 
     6 class A{
     7 public:
     8      int ai;
     9      A (int i): ai(i) { }
    10 };
    11 
    12 //这种情况必须在引用前加const,否则会出错。目前本人的觉得可能是因为临时对象具有常性
    13 void test(const int &ti, const A &t) 
    14 {
    15     cout << "子线程开始" << endl;
    16     //ti的内存地址0x0126d2ec {4},t.ai的内存地址0x0126d2e8 {ai=5 }
    17     cout << ti << " " << t.ai << endl;  
    18     cout << "子线程结束" << endl;
    19     return;
    20 }
    21 
    22 
    23 int main()
    24 {
    25     cout << "主线程开始" << endl;
    26     //i的内存地址0x010ff834 {4},a的内存地址0x010ff828 {ai=5 }
    27     int i = 4;  
    28     A a = A(5);
    29     thread t(test, i, a);
    30     t.join();
    31     cout << "主线程结束!" << endl;
    32     return 0;
    33 }

       那么如果我们真的需要像一般程序那样传递引用呢,即在子线程中的修改能够反映到主线程中。此时需要使用std::ref()但是注意如果我们会在子线中改变它,此时用于接收ref()的那个参数前不能加const。关于C++多线程中的参数传引用问题,我目前只是记住了这个现象,关于原理还需后期研究源码继续学习。

     1 #include <iostream>
     2 #include <thread>
     3 
     4 using namespace std;
     5 
     6 class A {
     7 public:
     8     int ai;
     9     A(int i) : ai(i) { }
    10 };
    11 
    12 //接收ref()的那个参数前不能加const,因为我们会改变那个值
    13 void test(int& ti, const A& t)
    14 {
    15     cout << "子线程开始" << endl;
    16     cout << ti << " " << t.ai << endl;
    17     ti++;
    18     cout << "子线程结束" << endl;
    19     return;
    20 }
    21 
    22 
    23 int main()
    24 {
    25     cout << "主线程开始" << endl;
    26     int i = 4;
    27     A a = A(5);
    28     thread t(test, ref(i), a);
    29     t.join();
    30     cout << "i改变:" << i << endl;
    31     cout << "主线程结束!" << endl;
    32     return 0;
    33 }

       传入类对象时,使用引用来接收比用值接收更高效。

     1 #include <iostream>
     2 #include <thread>
     3 
     4 using namespace std;
     5 
     6 class A {
     7 public:
     8     int ai;
     9     A (int i) : ai(i) 
    10     { 
    11         cout << "构造" << this << endl; 
    12     }
    13 
    14     A (const A& a) :ai(a.ai) {
    15         cout << "拷贝构造" << this << endl;
    16     }
    17 
    18     ~A()
    19     {
    20         cout << "析构" << this << endl;
    21     }
    22 };
    23 
    24 //void test(const A a)
    25 void test(const A& a)
    26 {
    27     cout << "子线程开始" << endl;
    28     cout << "子线程结束" << endl;
    29     return;
    30 }
    31 
    32 
    33 int main()
    34 {
    35     cout << "主线程开始" << endl;
    36     int i = 4;
    37     thread t(test, A(i));
    38     t.join();
    39     cout << "主线程结束!" << endl;
    40     return 0;
    41 }

     

    传指针

      从下面的运行结果,可以看出,主线程和子线程中的指针都是指向同一块内存。所以在这种情况下会有一个陷阱,如果使用detach(),则当主线程崩溃或者正常结束后,该块内存被回收,若此时子线程没有结束,那么子线程中指针的访问将未定义,程序会出错。

     1 #include <iostream>
     2 #include <thread>
     3 
     4 using namespace std;
     5 
     6 
     7 void test(char *p) 
     8 {
     9     cout << "子线程开始" << endl;
    10     //0x004ffeb4 "hello"
    11     cout << p << endl;  
    12     cout << "子线程结束" << endl;
    13     return;
    14 }
    15 
    16 
    17 int main()
    18 {
    19     cout << "主线程开始" << endl;
    20     //0x004ffeb4 "hello"
    21     char s[] = "hello";
    22     thread t(test, s);
    23     t.join();
    24     cout << "主线程结束!" << endl;
    25     return 0;
    26 }

    传临时对象

       用临时变量作为实参时,会更高效,由于临时变量会隐式自动进行移动操作,这就减少了整体构造函数的调用次数。而一个命名变量的移动操作就需要std::move()。

     1 #include <iostream>
     2 #include <thread>
     3 
     4 using namespace std;
     5 
     6 class A {
     7 public:
     8     int ai;
     9     A (int i) : ai(i) 
    10     { 
    11         cout << "构造" << this << endl; 
    12     }
    13 
    14     A (const A& a) :ai(a.ai) {
    15         cout << "拷贝构造" << this << endl;
    16     }
    17 
    18     ~A()
    19     {
    20         cout << "析构" << this << endl;
    21     }
    22 };
    23 
    24 void test(const A& a)
    25 {
    26     cout << "子线程开始" << endl;
    27     cout << "子线程结束" << endl;
    28     return;
    29 }
    30 
    31 
    32 int main()
    33 {
    34     cout << "主线程开始" << endl;
    35     int i = 4;
    36     thread t(test, A(i));
    37     t.join();
    38     cout << "主线程结束!" << endl;
    39     return 0;
    40 }

    总结

      1、使用引用和指针是要注意;

      2、对于内置简单类型,建议传值;

      3、对于类对象,建议使用引用来接收,以为使用引用会只会构造两次,而传值会构造三次;

      4、在detach下要避免隐式转换,因为此时子线程可能还来不及转换主线程就结束了,应该在构造线程时,用参数构造一个临时对象传入。

  • 相关阅读:
    (转载)安装Android时Could not find D:\Android\a...sdkwindows\tools\adb.exe
    (转载)Exception in thread "main" java.lang.NoClassDefFoundError错误信息
    (转载)java函数参数类型后添加三点的用法
    (转载)一个fork的面试题
    javascript event事件再次封装了,兼容大多数浏览器
    SINA多功能弹窗已经摘出来
    Observer Design Pattern Using JavaScript
    CSS Hacks for IE,IE 也可以很完美
    N久前写的一个JSONP请求方法
    JS模拟滚动条雏形(待进一步规整)
  • 原文地址:https://www.cnblogs.com/chen-cs/p/13056703.html
Copyright © 2020-2023  润新知